2

For example, given the following code

class A {
 public:
    double operator()(double foo) {
        return foo;
    }
};

class B {
 public:
    double operator()(double foo, int bar) {
        return foo + bar;
    }
};

I want to write two versions of fun, one that works with objects with A's signature and another one that works with objects with B's signature:

template <typename F, typename T>
T fun(F f, T t) {
    return f(t);
}

template <typename F, typename T>
T fun(F f, T t) {
    return f(t, 2);
}

And I expect this behavior

A a();
B b();
fun(a, 4.0);  // I want this to be 4.0
fun(b, 4.0);  // I want this to be 6.0

Of course the previous example throws a template redefinition error at compile time.

If B is a function instead, I can rewrite fun to be something like this:

template <typename T>
T fun(T (f)(T, int), T t) {
    return f(t, 2);
}

But I want fun to work with both, functions and callable objects. Using std::bind or std::function maybe would solve the problem, but I'm using C++98 and those were introduced in C++11.

texdditor
  • 105
  • 1
  • 1
  • 9
  • It is not really possible. A single callable object can have more than one signature (e.g. an overloaded `operator()`). – n. m. could be an AI Apr 09 '18 at 19:42
  • Something similar is possible in C++>=11 but 98 seems too weak. – n. m. could be an AI Apr 09 '18 at 19:49
  • I thought [this question](https://stackoverflow.com/questions/27324124/overload-function-template-based-on-function-object-operator-signature-in-c?rq=1) was accomplishing something similar, but I found the code too confusing. @n.m. I don't really see how a callable object having more than one signature can affect the template deduction. – texdditor Apr 09 '18 at 20:14
  • 1
    The linked answer is based on sizeof(value-returned-by-call) which breaks down when the target function returns void. But if this isn't important to you it's possible to use this approach. – n. m. could be an AI Apr 09 '18 at 20:41
  • In some of my cases, the function returns `void` so no dice. – texdditor Apr 09 '18 at 20:58
  • 1
    Wait I think I have a way, hold on... – n. m. could be an AI Apr 10 '18 at 02:55

1 Answers1

1

Here's a solution modified from this question to accommodate void-returning functions. The solution is simply to use sizeof(possibly-void-expression, 1).

#include <cstdlib>
#include <iostream>

// like std::declval in c++11
template <typename T>
T& decl_val();

// just use the type and ignore the value. 
template <std::size_t, typename T = void> 
struct ignore_value {typedef T type;};

// This is basic expression-based SFINAE.
// If the expression inside sizeof() is invalid, substitution fails.
// The expression, when valid, is always of type int, 
// thanks to the comma operator.
// The expression is valid if an F is callable with specified parameters. 
template <class F>
typename ignore_value<sizeof(decl_val<F>()(1),1), void>::type
call(F f)
{
    f(1);
}

// Same, with different parameters passed to an F.
template <class F>
typename ignore_value<sizeof(decl_val<F>()(1,1),1), void>::type
call(F f)
{
    f(1, 2);
}

void func1(int) { std::cout << "func1\n"; }
void func2(int,int) { std::cout << "func2\n"; }

struct A
{
    void operator()(int){ std::cout << "A\n"; }
};

struct B
{
    void operator()(int, int){ std::cout << "B\n"; }
};

struct C
{
    void operator()(int){ std::cout << "C1\n"; }
    void operator()(int, int){ std::cout << "C2\n"; }
};

int main()
{
    call(func1);
    call(func2);
    call(A());
    call(B());
    // call(C()); // ambiguous
}

Checked with gcc and clang in c++98 mode.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243