1

I am trying to modify the is_detected idiom to allow passing variadic arguments to it. I need this since some of my detected member functions will have user provided arguments.

So far, this is what I got working. You give the extra args to is_detected_args_v, and in theory, the template specialization would kick in and compile correctly. Thus giving std::true_type.

#include <type_traits>
#include <cstdio>

// slightly modified (and simplified) is_detected
template <template <class, class...> class Op, class T, class = void, class...>
struct is_detected_args : std::false_type {};
template <template <class, class...> class Op, class T, class... Args>
struct is_detected_args<Op, T, std::void_t<Op<T, Args...>>, Args...>
        : std::true_type {};

template <template <class, class...> class Op, class T, class... Args>
inline constexpr bool is_detected_args_v
        = is_detected_args<Op, T, Args...>::value;

// has_func, checks the function starts with int, and then Args&...
template <class T, class... Args>
using has_func = decltype(std::declval<T>().func(
        std::declval<int>(), std::declval<Args&>()...));


// has the func
struct obj {
    void func(int, double&, double&) {
        printf("potato\n");
    }
};

int main(int, char**) {
    obj o;

    if constexpr(is_detected_args_v<has_func, obj, double, double>) {
        double d = 0;
        double d2 = 42;
        o.func(42, d, d2);
    }
}

You can run the example here (tested on all 3 compilers) : https://wandbox.org/permlink/ttCmWSVl1XVZjty7

The problem is, the specialization is never chosen and the conditional is always false. My question is two-folds.

  1. Is this even possible?
  2. Why doesn't is_detected get specialized?

Thx

Barry
  • 286,269
  • 29
  • 621
  • 977
scx
  • 3,221
  • 1
  • 19
  • 37

1 Answers1

1

The main issue here is misunderstanding what void_t does. As a refresher, see how does void_t work?. The key idea is that the primary template has a void parameter and the specialization has some complex thing that you want to check wrapped in void_t so that it matches the primary template's parameter. That isn't happening in your example.

We can fix it in two easy steps. First, you have this type T along with Args... There isn't actually any reason to split this up, and it's easier to look at if we don't have extraneous parameters. So here's your attempt just reduced (I also gave a name to the parameter which is supposed to be void):

template <template <class...> class Op, class AlwaysVoid, class...>
struct is_detected_args : std::false_type {};
template <template <class...> class Op, class... Args>
struct is_detected_args<Op, std::void_t<Op<Args...>>, Args...>
        : std::true_type {};

template <template <class...> class Op, class... Args>
inline constexpr bool is_detected_args_v = is_detected_args<Op, Args...>::value;

Now it should be easier to see what's missing: the void parameter! You're not passing in a void and you need to. That's an easy fix though:

template <template <class...> class Op, class... Args>
inline constexpr bool is_detected_args_v = is_detected_args<Op, void, Args...>::value;
//                                                              ~~~~~

And now it works as expected.


Cppreference also provides a complete implementation of is_detected if you want to look at that too.

Barry
  • 286,269
  • 29
  • 621
  • 977