mercredi 3 juin 2015

Variadic templates with defaulted parameters

First some background. The origin of my question (and the source of immense frustration for me right now) is that I'm trying to get libc++ to be usable with MSVC. Compiling the library itself didn't prove too tricky, but getting the templates to compile certainly is.

One big hurdle I've hit recently is with trying to compile certain headers with variadic templates enabled. Specifically, in the header there is code for templated __invoke functions (for generically invoking function types with variadic parameters).

I've extracted some of the code to provide a stand alone snippet. The myInvoke templates are based on the __invoke ones in libc++. Some comments in the code to explain what's going on (apologies, this is not a small snippet!):

#include <type_traits>

using namespace std;

// Declarations of myinvoke templates.  Both of them are designed to take a 
// member function pointer _Fp, a class instance _A0 (which _Fp is assumed
// to point to a member of) and a variadic list of arguments for _Fp.

// The first one handles the case where _A0 is an instance of a class which is
// derived from the class to which _Fp is a member of.

// The second one handles the case when _A0 is a pointer to an instance of a 
// class which is derived from the class to which _Fp is a member of.

template <class _Fp, class _A0, class ..._Args,
class = typename enable_if
    <
    is_member_function_pointer<typename remove_reference<_Fp>::type>::value &&
    is_base_of<typename remove_reference<typename __member_pointer_traits<typename remove_reference<_Fp>::type>::_ClassType>::type,
    typename remove_reference<_A0>::type>::value
    >::type
>
auto
myinvoke(_Fp&& __f, _A0&& __a0, _Args&& ...__args)
-> decltype((std::forward<_A0>(__a0).*__f)(std::forward<_Args>(__args)...));

template <class _Fp, class _A0, class ..._Args,
class = typename enable_if
    <
    is_member_function_pointer<typename remove_reference<_Fp>::type>::value &&
    !is_base_of<typename remove_reference<typename __member_pointer_traits<typename remove_reference<_Fp>::type>::_ClassType>::type,
    typename remove_reference<_A0>::type>::value
    >::type
>
auto
myinvoke(_Fp&& __f, _A0&& __a0, _Args&& ...__args)
-> decltype(((*std::forward<_A0>(__a0)).*__f)(std::forward<_Args>(__args)...));


// Definitions of myinvoke templates.  

template <class _Fp, class _A0, class ..._Args, class>
inline
auto
myinvoke(_Fp&& __f, _A0&& __a0, _Args&& ...__args)
    -> decltype((std::forward<_A0>(__a0).*__f)(std::forward<_Args>(__args)...))
{
    return (std::forward<_A0>(__a0).*__f)(std::forward<_Args>(__args)...);
}

template <class _Fp, class _A0, class ..._Args, class>
inline 
auto
myinvoke(_Fp&& __f, _A0&& __a0, _Args&& ...__args)
    -> decltype(((*std::forward<_A0>(__a0)).*__f)(std::forward<_Args>(__args)...))
{
    return ((*std::forward<_A0>(__a0)).*__f)(std::forward<_Args>(__args)...);
}

// Some class with a member which we can create a member fn pointer to.
struct SomeClass
{
    typedef double(SomeClass::*foo_t)(int, float, double);
    double foo(int a, float b, double c){return 0.0;}
};

int main()
{
    SomeClass sc;
    SomeClass::foo_t pFoo = &SomeClass::foo;

    // Invoke the first template 
    myinvoke(pFoo, sc, 0, 0.0f, 0.0);

    // Invoke the second template.
    myinvoke(pFoo, &sc, 0, 0.0f, 0.0);

    return 0;
}

This code compiles fine with GCC, however it fails with MSVC (using either libc++ or MSVC's type_traits header). I'll come onto the error in a moment, but first I need to clear up some points on templating.

Question 1:

Can someone point me to a specific part of the standard specification, some specific documentation or give me some official lingo for this trick of defaulting a parameter after the parameter pack.

Question 2:

How does the compiler pick the correct definition (implementation) of the template? It is clear to me how the correct declaration is chosen as SFINAE means only one can work. However, the definitions are indistinguishable apart from the trailing return type. Does SFINAE also apply in the scope of trailing return types? If that's the case then of course only only one of the decltype statements can compile so only one implementation would be applicable (in the context of each myInvoke call). However, this is still strange (to me at least) as the compiler will need to instanciate both definitions of the template - one for each call of myInvoke. Each one will ultimately have the same signature as the return types are the same in both cases (although resolved in a different way). Does this mean the compiler actually sees these two instances of the template as different? If so, can someone point me to some documentation of this as well please?

Onto the MSVC error

1>test.cpp(165): warning C4348: 'myinvoke' : redefinition of default parameter : parameter 4

1> test.cpp(162) : see declaration of 'myinvoke'

1>test.cpp(196): error C2995: 'unknown-type myinvoke(_Fp &&,_A0 &&,_Args &&...)' : function template has already been defined

1> test.cpp(162) : see declaration of 'myinvoke'

1>test.cpp(211): error C3861: 'myinvoke': identifier not found

1>test.cpp(214): error C3861: 'myinvoke': identifier not found

(To get the appropriate line numbers to match up, just copy paste my sample into: http://ift.tt/1qXV1Ol)

It's probable that a clear answer to Question 1 will help me solve this problem but:

Question 3:

What part of MSVC non-conformance is causing this? Is it indeed non-conformance or is it possibly the case that GCC/Clang support some extensions to the language?

Thanks in advance. I'm off to get coffee and pretend that templates don't exist (this is only the tip of the iceberg in terms of the "fun" I'm having!).

Aucun commentaire:

Enregistrer un commentaire