Как я могу перегрузить функцию вызываемым объектом в качестве параметра, основанного на сигнатуре вызова объекта?
Например, учитывая следующий код
class A {
public:
double operator()(double foo) {
return foo;
}
};
class B {
public:
double operator()(double foo, int bar) {
return foo + bar;
}
};
Я хочу написать две версии fun
один работает с объектами с подписью А, а другой - с объектами с подписью Б:
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);
}
И я ожидаю, что это поведение
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
Конечно, предыдущий пример генерирует ошибку переопределения шаблона во время компиляции.
Если B - функция вместо этого, я могу переписать fun
быть примерно таким:
template <typename T>
T fun(T (f)(T, int), T t) {
return f(t, 2);
}
Но я хочу fun
работать как с функциями, так и с вызываемыми объектами. С помощью std::bind
или же std::function
возможно решит проблему, но я использую C++98, и они были введены в C++11.
1 ответ
Решение
Вот решение, измененное из этого вопроса, чтобы приспособить функции, возвращающие пустоту. Решение просто в использовании 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
}
Проверено с помощью gcc и clang в режиме C++98.