MSVC 2012通过SFINAE检测模板功能的模板参数数量

And*_*ord 5 c++ templates sfinae visual-c++ c++11

我正在尝试做的事情:我有一个模板对象传入,作为一个接口的一部分,应该有一个"进程"函数定义了一些参数(我不知道有多少),其中一些是模板参数.

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

都是有效的处理程序.所以现在我需要检测处理程序的签名:静态类型参数和许多模板参数.

为此,我使用了许多模板魔术黑客,可以缩小到有问题的部分 - 检测一些模板args(或只是检索模板化签名).

我试图找出所需信息的方法使用描述的方法检查明确专用的签名是否可以编写模板来检查函数的存在?

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;
}
Run Code Online (Sandbox Code Playgroud)

使用clang(使用msvc2013或linux版本构建,使用gcc-4.9.2构建),它编译并输出:

 A::process 0
 B::process 1
Run Code Online (Sandbox Code Playgroud)

使用msvc2012,它也会编译,但输出:

 A::process 0
 B::process -1
Run Code Online (Sandbox Code Playgroud)

当通过评论后备案例(带有(...)的那个)进入角色时,msvc2012吓坏了:

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'
Run Code Online (Sandbox Code Playgroud)

(稍微重新格式化日志以减少行数)

我也试过使用上面问题的评论中描述的不同技术(使用char [sizeof],使用typeof并将检查移动到返回类型中)无济于事 - 它产生相同的结果或者甚至更奇怪地分崩离析错误(包括"意外的文件结尾",没有明显的原因).

我还检查了一个类似的问题Deduce variadic args和functor模板参数(MSVC特定的)返回类型与另一种技术(通过SFINAE比较原型),但我不知道如何使用它,当我不知道确切的签名(即我不知道静态参数的数量和类型).当然,我可能会蛮力强迫他们完成手头的特定任务,但......

所以我有两个问题:

  1. 为什么MSVC应该总是如此努力?好吧,这是一个修辞,不需要回答.
  2. 我是否滥用了一些clang/gcc,实际上MSVC通过在我脸上抛出毫无意义的错误来做正确的事情?除了强制所有可能的静态/模板参数组合并使用完整的签名原型进行比较之外,是否有任何变通方法或正确的方法来做到这一点?

sla*_*ppy 1

这里的问题是 MSVC 拒绝提取要在重载解析中使用的类型&C::template process<_D>(请注意,没有任何有意义的错误消息)。看起来它认为实例化函数是仅一个函数的重载集;可能是一个实施错误。

您可以通过将重载集输入到函数参数中来强制它将重载集转换为函数指针类型:

template<typename T>
T* fn_id(T* func) {
    return nullptr;
}
Run Code Online (Sandbox Code Playgroud)

一旦T被扁平化为函数类型,您就可以将其用于decltype.

template <typename C>
    static R<1> retrieve(decltype(fn_id(&C::template process<_D>)));
Run Code Online (Sandbox Code Playgroud)

通过这个 hack,我得到了与 clang 相同的输出。