在C++11中使用SFINAE在具有相同签名的两个函数之间进行选择

在C++11中使用SFINAE在具有相同签名的两个函数之间进行选择,c++,templates,c++11,sfinae,C++,Templates,C++11,Sfinae,我试图在c++11中使用SFINAE在两个具有相同签名但不同主体的函数之间进行选择,这是基于给定T类型中是否应该有typedef这一事实。例如,我有两个结构,一个定义了MyTypedef,另一个什么都没有: struct MyStruct1 { typedef std::false_type MyTypedef; ... } struct MyStruct3 { ... } 然后我有两个模板函数,它们应该将结构的类型作为T,并根据MyTypedef的存在执行不同的操

我试图在c++11中使用SFINAE在两个具有相同签名但不同主体的函数之间进行选择,这是基于给定T类型中是否应该有typedef这一事实。例如,我有两个结构,一个定义了MyTypedef,另一个什么都没有:

struct MyStruct1
{
    typedef std::false_type MyTypedef;

    ...
}

struct MyStruct3
{
    ...
}
然后我有两个模板函数,它们应该将结构的类型作为T,并根据MyTypedef的存在执行不同的操作。特别是,如果定义了T::MyTypedef,我应该调用第一个函数,在所有其他情况下,我应该调用第二个函数。我试过这样的方法:

template<typename T>
void myFunction()
{
    ...
}

template<typename T, typename std::enable_if<T::MyTypedef>::type* = nullptr>
typename myFunction()
{
    ...
}

但它没有编译,错误是C2668 MSVC-“function”:对重载函数的不明确调用

您可以编写一个trait来检测typedef是否存在,例如使用void_t和SFINAE,基于此:

template<class...> struct voider { using type = void; };
template<class... Ts> using void_t = typename voider<Ts...>::type;

template<class T, class = void>
struct has_MyTypedef : std::false_type {};

template<class T>
struct has_MyTypedef<T, void_t<typename T::MyTypedef>> : std::true_type {};

template<typename T, std::enable_if_t<has_MyTypedef<T>{}, int> = 0>
void myFunction()
{
    ...
}

template<typename T, std::enable_if_t<!has_MyTypedef<T>{}, int> = 0>
void myFunction()
{
    ...
}
或者编写内部助手,以便在两种可行情况下使用伪参数消除它们之间的歧义:

template<class T, class = typename T::MyTypedef>
void myFunctionImpl(int) {
    // ...
}
template<class T>
void myFunctionImpl(...) {
    // ...
}

template<class T>
void myFunction(){
    return myFunctionImpl<T>(0);
}

您的代码无法编译,因为在has typedef情况下,两个重载都存在且有效。编译器无法在它们之间进行选择。有3种基本方法

首先,简单地反转条件。以sfinae友好的方式编写有typedef和没有typedef的函数,如果有,则启用一个函数,如果没有,则启用另一个函数

我发现这通常是丑陋的和规模很差

第二种方法是,当两者都有效时,通过使用重载解析顺序技巧继承、varargs、转换等,使其成为首选。这个比例稍微好一点,但我发现它有点粗糙,有点脆

第三种方法是标记分派。这不支持没有有效的重载井,但在其他方面是干净的

您可以编写一个基函数。此函数执行一些类型计算,并使用结果构建一些标记类型。然后,使用标记调用实现函数,并使用它进行调度

想象一下,你有着乌福的特质。如果类型T具有属性foo,则它是从std::true\u type继承的类型,否则为false\u type

然后我们可以:

 void bar_impl(std::true_type);
 void bar_impl(std::false_type);

template<class T>
void bar(T  const&){
  bar_impl( has_foo<T>{} );
}
然后我们只需编写测试:

template<class T>
using bob_subtype = typename T::bob;

完成。

C++0x不再存在,它是C++11的一个草稿名称。您还必须禁用第一个重载,当第二个重载启用时,我通过删除问题不需要的大小写简化了示例代码。您能否解释一下,只有在定义了T::MyTypedef的情况下,如何禁用第一个重载并启用第二个重载?此外,typedef是一种糟糕的、老式的定义别名的方法,我建议您在可能的情况下开始使用。@Stefano:如果您想在这样的重载集上使用SFINAE,那么,模板参数决不能导致两个重载都可见。在您的情况下,您的第一个重载必须与SFINAE测试相反。我不知道如何进行这样的测试,否则我会发布一个答案。您的第一个代码正是我想要做的,但它没有编译。为了成功地使用它,我不得不将一个模板换成另一个模板。你能解释一下它是如何工作的吗?虽然我也喜欢你的解决方案,但我发现它比T.C.的解决方案更复杂,我真的不想有一个带有假参数的内部方法。另外,在我的案例中,有两个条件的SFINAE更简单,因为我知道我永远不会有超过两个案例需要处理。如果是不同的,您的标记分派方法可能会更好
template<class T>
using bob_subtype = typename T::bob;
template<class T>
using has_bob=can_apply<bob_subtype,T>;