简体   繁体   中英

Enable template if functor passed as argument takes no argument

I am trying to define two overload for a template function. First overload is generated if functor passed as an argument has no parameter otherwise second overload is generated. I started to implement like this :

template <typename R, typename... Types>
constexpr size_t argumentCount(R (*f)(Types...))
{
    return sizeof...(Types);
}

template <typename Function>
typename std::enable_if<argumentCount<Function>() == 0, int>::value = 0 > callFunc(Function fu)
{
    fu();
}

template <typename Function, typename... Params>
typename std::enable_if<argumentCount<Function>() == 0, int>::value = 0 > callFunc(Function fu, Params... params)
{
    fu(std::forward<decltype(params)>(params)...);
}

This does not compile for several reasons including parse error.What I want to do is,

callFunc([](){}); will call overload 1 while callFunc([](int value1,int value2){}); will call overload 2.

How can I achieve this ?

You can use the detection idiom with std::void_t and std::declval to detect such features.

template <typename FuncT>
using invocable_without_args_t = std::void_t<decltype(std::declval<FuncT>()())>;

More information is available here ; this post helped me a lot to understand how this works.

  1. Here is your version with a fixed syntax:
template <typename R, typename... Types>
constexpr size_t argumentCount(R (*f)(Types...))
{
    return sizeof...(Types);
}

template <typename Function>
typename std::enable_if<argumentCount<Function>() == 0, int> callFunc(Function fu)
{
    fu();
}

template <typename Function, typename... Params>
typename std::enable_if<argumentCount<Function>() == 0, int> callFunc(Function fu, Params... params)
{
    fu(std::forward<decltype(params)>(params)...);
}
  1. In this particular example you do not need an extra overload, Types... can be empty.
    template <typename Function, typename... Params>
    auto callFunc(Function fu, Params... params)
    {
        fu(std::forward<decltype(params)>(params)...);
    }

works perfectly well.

If you can add a level of indirection... what about using tag-dispatching instead of SFINAE?

I mean, something as follows

#include <iostream>
#include <type_traits>

template <typename F>
void callFunc_helper (F fu, std::true_type)
 {
   std::cout << "zero version" << std::endl;

   fu();
 }

template <typename F, typename... Prs>
void callFunc_helper (F fu, std::false_type, Prs && ... params)
 {
   std::cout << "non zero version" << std::endl;

   fu(std::forward<Prs>(params)...);
 }

template <typename F, typename... Prs>
void callFunc (F fu, Prs && ... params)
 { callFunc_helper(fu, std::integral_constant<bool, 0u == sizeof...(Prs)>{},
                   std::forward<Prs>(params)...); }

int main ()
 {
   callFunc([]{});
   callFunc([](int, int){}, 0, 1);
 }

Obviously you can use std::integral_constant<bool, 0u == argumentCount<Function>()>{} if you really want to check the number of the functional arguments instead of the number of the following parameters (but why?).

If you need two template functions, just write them:

#include <iostream>

template <class R>
void callFunc(R (*f)()) {
    std::cout << "Called no-arg template\n";
    f();
}

template <class R, class T, class... Types, class... Params>
void callFunc(R (*f)(T, Types...), Params... params) {
    std::cout << "Called multi-arg template\n";
    f(params...);
}

void g() {
    std::cout << "Called g\n";
}

void h(int) {
    std::cout << "Called h\n";
}

int main() {
    callFunc(g);
    callFunc(h, 3);
    return 0;
}

Output:

[temp]$ clang++ -std=c++11 test.cpp
[temp]$ ./a.out
Called no-arg template
Called g
Called multi-arg template
Called h
[temp]$ 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM