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

问题描述 投票:0回答:3

我尝试使用 c++11 中的 SFINAE 在具有相同签名但不同主体的两个函数之间进行选择,基于给定

typedef
类型内应该有或没有
T
的事实。例如,我有这两个结构,一个定义了 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' : 对重载函数的不明确调用

c++ templates c++11 sfinae
3个回答
5
投票

您可以编写一个特征来检测 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);
}

1
投票

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

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

我发现这通常很丑陋并且扩展性很差。

第二种方法是通过使用重载解析顺序技巧(继承、可变参数、转换等),当两者都有效时,首选您想要的那个。这个扩展稍微好一些,但我发现它很老套而且有点脆弱。

第三种方法是标签调度。这不能很好地支持“没有有效的重载”,但在其他方面是干净的。

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

想象你具有

has_foo<T>
的特质。如果类型
std::true_type
具有属性
T
,则该类型继承自
foo
,否则为
false_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>{} );
}

并且如果

bar_impl
具有属性
T
则调用对应的
foo

编写这样的测试很容易。这是一个小图书馆:

 namespace details{
   template<template<class...>class Z, class, class...>
   struct can_apply:std::false_type{};
   template<template<class...>class Z, class...Ts>
   struct can_apply<Z, decltype(void(std::declval<Z<Ts...>>())), Ts...>:
     std::true_type
   {};
 }
 template<template<class...>class Z,class...Ts>
 using can_apply=typename details::can_apply<Z,void,Ts...>::type;

然后我们只需编写测试:

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

是类型

T::bob
。我们可以测试某个东西是否有鲍勃:

template<class T>
using has_bob=can_apply<bob_subtype,T>;

完成了。


0
投票

当我最终决定搜索堆栈溢出时,我花了几个小时试图解决这个确切的问题,并很高兴看到 8 年前就有人问过这个问题;但当我无法编译可接受的解决方案时很快就感到失望。 (即使在应用 Stefano 作为评论指出的修复程序之后也没有。)也许我只是未能正确地将事情组合在一起 - 不确定。但即使我能让它工作,我也不确定我是否能破译它的工作原理。 (我也接受这里的指责,因为我发现很多 SFINAE 诡计难以破译。) 综上所述,我想我已经想出了一个更简单的解决方案。 (我能够想到它的事实有力地证明了它更简单。)在这里,我决定使用

constexpr static bool prop = true;

作为可选的结构体属性,而不是

typedef std::false_type MyTypedef;
,只是因为我觉得它更简单。也就是说,我确信如果有人真的喜欢的话,我可以调整代码以引用 typedef。
在未定义 

T::prop

的情况下,通过在函数签名中引用 T::prop 来让 myFunction 的两个版本之一进行

not
编译是很容易的。问题(显然)是想出一种方法,在定义了
T::prop
时,让 myFunction 的 other 版本无法编译。最终我想到,我可以定义一个子类(下面名为 double_inheritance),它继承自 T(感兴趣的结构)和另一个辅助类(下面称为 false_prop),其中
also
定义 prop (具有 false 值 -尽管值为 false 实际上并不重要)。然后 myFunction 的第二个实现引用了 double_inheritance::prop。对于未定义 T::prop 的情况,它可以正常编译,因为 prop 是在 false_prop 中定义的;但对于还定义了 T::prop is 的情况,您会收到编译错误,因为引用不明确(因为它是在两个基类中定义的)。这种歧义的引入给我们带来了我们需要的编译错误,使 myFunction 的第一个实现成为唯一可行的候选者。 我添加了一点 who_am_i() 来标识负责调用两个 myFunctions 的结构类型。 #include <iostream> #include <type_traits> template <typename T, typename U> struct double_inheritance : public T, public U { }; struct false_prop { constexpr static bool prop = false; }; template<typename T, std::enable_if_t<T::prop, bool> = true> void myFunction() { std::cout << "In FIRST myFunction for class " << T::who_am_i() << std::endl; } template<typename T, std::enable_if_t<!double_inheritance<false_prop,T>::prop, bool> = false> void myFunction() { std::cout << "In SECOND myFunction for class " << T::who_am_i() << std::endl; } struct MyStruct1 { constexpr static bool prop = true; static std::string who_am_i() { return "MyStruct1"; } }; struct MyStruct3 { static std::string who_am_i() { return "MyStruct3"; } }; int main() { myFunction<MyStruct1>(); myFunction<MyStruct3>(); return 0; }

标准输出:

In FIRST myFunction for class MyStruct1
In SECOND myFunction for class MyStruct3

© www.soinside.com 2019 - 2024. All rights reserved.