Ну ... то, что вы спрашиваете, не совсем тривиально ...
Прежде всего: я не считаю хорошей идеей принимать возвращаемое значение по умолчанию в качестве параметров шаблона: работает для целочисленных типов, но, например, не работает для типов с плавающей запятой. Вы можете обойти эту проблему, но я предлагаю передать значение по умолчанию в качестве значения в конструкторе.
Второе: я предлагаю следующий код, чтобы определить, можно ли вызвать вызываемый объект типа F
id с данным списком типов
template <typename ...>
constexpr std::false_type isInvocableWithHelper (long);
template <typename F, typename ... Args>
constexpr auto isInvocableWithHelper (int)
-> decltype( std::declval<F>()
(std::forward<Args>(std::declval<Args>())...),
std::true_type{} );
template <typename F, typename ... Args>
using isInvocableWith = decltype(isInvocableWithHelper<F, Args...>(0));
Теперь, используя делегирующие конструкторы и диспетчеризацию тегов, ваш Adapter
конструктор может быть
template <typename F>
Adapter (F const & f, RetT defVal = RetT{})
: Adapter{f, defVal, isInvocableWith<F, Args...>{},
isInvocableWith<F>{}}
{ }
где третий аргумент (isInvocableWith<F, Args...>{}
) является истинным (std::true_type
), только если объект F
можно вызвать со списком объектов Args...
, а четвертый аргумент (isInvocableWith<F>{}
) является истинным (std::true_type
) *) только если объект F
можно вызвать без аргумента.
Теперь вы должны определить, возвращает ли F
(объект типа F
) void
или значение (предположительно RetT
или что-то, конвертируемое в RetT
).
Вы можете снова использовать делегирование конструкторов и диспетчеризацию тегов с помощью
template <typename F>
Adapter (F const & f, RetT const & defVal, std::true_type const &,
std::false_type const &)
: Adapter{f, defVal, std::true_type{}, std::false_type{},
std::is_same<void,
decltype(f(std::forward<Args>(std::declval<Args>())...))>{}}
{ }
в случае вызова с Args...
аргументами (обратите внимание, что последними аргументами являются std::true_type
(наследовать от std::true_type
), если f
return void
).
В случае f
, вызываемого без аргументов, конструктор становится
template <typename F>
Adapter (F const & f, RetT const & defVal, std::false_type const &,
std::true_type const &)
: Adapter{f, defVal, std::false_type{}, std::true_type{},
std::is_same<void, decltype(f())>{}}
{ }
Теперь четыре финальных конструктора
template <typename F>
Adapter (F const & f, RetT const & defVal, std::true_type const &,
std::false_type const &, std::true_type const &)
: defV{defVal}
{
func = [&, this](Args && ... as)
{ f(std::forward<Args>(as)...); return defV; };
std::cout << "--- case 1 (full, void)" << std::endl;
}
template <typename F>
Adapter (F const & f, RetT const &, std::true_type const &,
std::false_type const &, std::false_type const &)
{
func = [&](Args && ... as)
{ return f(std::forward<Args>(as)...); };
std::cout << "--- case 2 (full, RetT)" << std::endl;
}
template <typename F>
Adapter (F const & f, RetT const & defVal, std::false_type const &,
std::true_type const &, std::true_type const &)
: defV{defVal}
{
func = [&, this](Args && ...)
{ f(); return defV; };
std::cout << "--- case 3 (noArgs, void)" << std::endl;
}
template <typename F>
Adapter (F const & f, RetT const &, std::false_type const &,
std::true_type const &, std::false_type const &)
{
func = [&](Args && ...)
{ return f(); };
std::cout << "--- case 4 (noArgs, RetT)" << std::endl;
}
Обратите внимание, что во всех случаях я создал лямбда-функцию, которая получает список аргументов Args...
и использует ее только в случае необходимости.
Я понимаю, что это немного сложно, но следующий пример в вашем ответе изменен в соответствии с этим решением.
#include <cstdio>
#include <iostream>
#include <functional>
template <typename ...>
constexpr std::false_type isInvocableWithHelper (long);
template <typename F, typename ... Args>
constexpr auto isInvocableWithHelper (int)
-> decltype( std::declval<F>()
(std::forward<Args>(std::declval<Args>())...),
std::true_type{} );
template <typename F, typename ... Args>
using isInvocableWith = decltype(isInvocableWithHelper<F, Args...>(0));
template <typename RetT, typename ... Args>
class Adapter
{
private:
std::function<RetT(Args ...)> func;
RetT defV { RetT{} };
template <typename F>
Adapter (F const & f, RetT const & defVal, std::true_type const &,
std::false_type const &, std::true_type const &)
: defV{defVal}
{
func = [&, this](Args && ... as)
{ f(std::forward<Args>(as)...); return defV; };
std::cout << "--- case 1 (full, void)" << std::endl;
}
template <typename F>
Adapter (F const & f, RetT const &, std::true_type const &,
std::false_type const &, std::false_type const &)
{
func = [&](Args && ... as)
{ return f(std::forward<Args>(as)...); };
std::cout << "--- case 2 (full, RetT)" << std::endl;
}
template <typename F>
Adapter (F const & f, RetT const & defVal, std::false_type const &,
std::true_type const &, std::true_type const &)
: defV{defVal}
{
func = [&, this](Args && ...)
{ f(); return defV; };
std::cout << "--- case 3 (noArgs, void)" << std::endl;
}
template <typename F>
Adapter (F const & f, RetT const &, std::false_type const &,
std::true_type const &, std::false_type const &)
{
func = [&](Args && ...)
{ return f(); };
std::cout << "--- case 4 (noArgs, RetT)" << std::endl;
}
template <typename F>
Adapter (F const & f, RetT const & defVal, std::true_type const &,
std::false_type const &)
: Adapter{f, defVal, std::true_type{}, std::false_type{},
std::is_same<void,
decltype(f(std::forward<Args>(std::declval<Args>())...))>{}}
{ }
template <typename F>
Adapter (F const & f, RetT const & defVal, std::false_type const &,
std::true_type const &)
: Adapter{f, defVal, std::false_type{}, std::true_type{},
std::is_same<void, decltype(f())>{}}
{ }
public:
template <typename F>
Adapter (F const & f, RetT defVal = RetT{})
: Adapter{f, defVal, isInvocableWith<F, Args...>{},
isInvocableWith<F>{}}
{ }
template <typename ... As>
RetT operator() (As && ... as) const
{ return func(std::forward<As>(as)...); }
};
void deaf_silent_f ()
{ puts("deaf/silent"); }
void silent_f (char const * arg)
{ printf("silent %s\n", arg); }
char const * deaf_f ()
{ puts("deaf"); return "deaf"; }
char const * normal_f (char const * arg)
{ printf("normal %s\n", arg); return "normal"; }
int main ()
{
typedef Adapter<char const *, char const *> A;
{
puts("function refs");
A ds(deaf_silent_f, "Def1"); printf("-> %s\n", ds("ds"));
A s(silent_f, "Def2"); printf("-> %s\n", s("s"));
A d(deaf_f); printf("-> %s\n", d("d"));
A n(normal_f); printf("-> %s\n", n("n"));
puts("");
}
{
puts("function pointers");
A ds(&deaf_silent_f, "Def3"); printf("-> %s\n", ds("ds"));
A s(&silent_f, "Def4"); printf("-> %s\n", s("s"));
A d(&deaf_f); printf("-> %s\n", d("d"));
A n(&normal_f); printf("-> %s\n", n("n"));
puts("");
}
{
puts("functors");
A ds([=]{ printf("deaf/silent %d\n", 42); }, "Def5");
printf("-> %s\n", ds("ds"));
A s([=](char const * a){ printf("silent(%s) %d\n", a, 42); }, "Def6");
printf("-> %s\n", s("s"));
A d([=]{ printf("deaf %d\n", 42); return "deaf"; });
printf("-> %s\n", d("d"));
A n([=](char const * a){ printf("normal(%s) %d\n", a, 42);
return "normal";});
printf("-> %s\n", n("n"));
}
}