Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MSVC 2012 detecting number of template arguments of a template function via SFINAE

What I'm trying to do: I've got a template object incoming which, as part of an interface, should have a "process" function defined with a number of arguments (I don't know how many) some of which are template arguments.

I.e.

struct A { static void process(int a); };
struct B { template <typename B0> static void process(int a, B0 b0); };

are both valid handlers to receive. So now I need to detect a signature for a handler: static-typed parameters and a number of template parameters.

To do so, I'm using a number of template magic hacks which may be narrowed down to the problematic part - detecting a number of template args (or just retrieving a templated signature).

The way I'm trying to find out the required info is by checking for a explicitly specialized signature using the method described in Is it possible to write a template to check for a function's existence?

struct _D;

template <typename T>
struct get_template_args_count
{
private:
    template <int count> struct R { enum { value = count }; };

    template <typename C>
    static R<0> retrieve(decltype(&C::process));

    template <typename C>
    static R<1> retrieve(decltype(&C::template process<_D>));

    template <typename C>
    static R<-1> retrieve(...);

public:
    typedef decltype(retrieve<T>(nullptr)) Result;
    enum { value = Result::value };
};

int main(int argc, char* argv[])
{
    std::cout
        << "A::process " << get_template_args_count<A>::value << "\n"
        << "B::process " << get_template_args_count<B>::value << "\n";
    std::cin.get();

    return 0;
}

With clang (built with msvc2013 or linux version, built with gcc-4.9.2) it compiles and outputs:

 A::process 0
 B::process 1

With msvc2012 it compiles too, but outputs:

 A::process 0
 B::process -1

When cornered in by commenting out the fallback case (the one with (...)) msvc2012 freaks out:

main.cpp(28): error C2893: Failed to specialize function template 'get_template_args_count<T>::R<count> get_template_args_count<T>::retrieve(unknown)'
          with [ T=B, count=1 ]
          With the following template arguments: 'B'
          v:\test\test\test\main.cpp(63) : see reference to class template instantiation 'get_template_args_count<T>' being compiled
          with [ T=B ]
main.cpp(28): error C2893: Failed to specialize function template 'get_template_args_count<T>::R<count> get_template_args_count<T>::retrieve(unknown)'
          with [ T=B, count=0 ]
          With the following template arguments: 'B'
main.cpp(29): error C2825: 'get_template_args_count<T>::Result': must be a class or namespace when followed by '::'
          with [ T=B ]
main.cpp(29): error C2039: 'value' : is not a member of '`global namespace''
main.cpp(29): error C2275: 'get_template_args_count<T>::Result' : illegal use of this type as an expression
          with [ T=B ]
main.cpp(29): error C2146: syntax error : missing '}' before identifier 'value'
main.cpp(29): error C2143: syntax error : missing ';' before '}'
main.cpp(29): error C2365: 'value' : redefinition; previous definition was 'enumerator'
        main.cpp(29) : see declaration of 'value'

(the log is slightly reformatted to take less lines)

I've also tried to use different techniques described in comments of the question above (using char[sizeof], using typeof and moving the check into return type), to no avail - it either produce the same results or falls apart with even weirder errors (including "unexpected end-of-file" with no obvious reason).

I've also checked out a similar question Deduce variadic args and return type from functor template parameter (MSVC-specific) with another technique (comparing prototypes via SFINAE), but I can't see how to use it when I don't know exact signature (i.e. I don't know a number and types of static parameters). I may brute-force them for a specific task at hand, of course, but...

So I've got two questions:

  1. Why it should be always so hard with MSVC?.. Ok, that's a rhetoric one, no answer is needed.
  2. Am I abusing some kindness of clang/gcc and actually MSVC is doing the right thing by throwing meaningless errors at my face? Are there any workarounds or right ways to do this, other than brute-forcing all possible static/template parameters combinations and comparing them using a full signature prototype?
like image 998
Andrian Nord Avatar asked Nov 09 '22 16:11

Andrian Nord


1 Answers

The problem here is that MSVC refuses to extract the type of &C::template process<_D> to use in overload resolution (Note the absence of any meaningful error message). It appears that it considers the instanciated function to be an overload-set of only one function; probably an implementation goof.

You can force it to convert the overload-set to a function pointer type by feeding it into a function parameter:

template<typename T>
T* fn_id(T* func) {
    return nullptr;
}

Once T has been flattened into a function type, you can use it into decltype.

template <typename C>
    static R<1> retrieve(decltype(fn_id(&C::template process<_D>)));

With this hack I get the same output as you had with clang.

like image 188
slaphappy Avatar answered Nov 15 '22 05:11

slaphappy