【问题标题】:Does SFINAE not apply here?SFINAE 在这里不适用吗?
【发布时间】:2014-12-28 17:46:32
【问题描述】:

我正在写一些东西来使用 SFINAE 在某些条件下不生成函数。当我直接使用元代码时,它按预期工作,但是当我通过另一个类间接使用代码时,它无法按预期工作。

我认为这是一个 VC++ 的东西,但看起来 g++ 也有这个,所以我想知道是否有某些原因 SFINAE 没有被应用于这个案例。

代码很简单。如果使用的类不是“集合类”的基类,则不要生成该函数。

#include <algorithm>
#include <type_traits>

#define USE_DIRECT 0
#define ENABLE 1

class A{};
class B{};
class C{};
class D{};
class collection1 : A, B, C {};
class collection2 : D {};

#if USE_DIRECT
template<typename X>
typename std::enable_if<std::is_base_of<X, collection1>::value, X>::type fn(X x)
{
    return X();
}

# if ENABLE
template<typename X>
typename std::enable_if<std::is_base_of<X, collection2>::value, X>::type fn(X x)
{
    return X();
}
# endif

#else // USE_DIRECT

template<typename X, typename COLLECTION>
struct enable_if_is_base_of
{
    static const int value = std::is_base_of<X, COLLECTION>::value;
    typedef typename std::enable_if<value, X>::type type;
};

template<typename X>
typename enable_if_is_base_of<X, collection1>::type fn(X x)
{
    return X();
}

# if ENABLE
template<typename X>
typename enable_if_is_base_of<X, collection2>::type fn(X x)
{
    return X();
}
# endif
#endif // USE_DIRECT

int main()
{
    fn(A());
    fn(B());
    fn(C());
    fn(D());

   return 0;
}

如果我将 USE_DIRECT 设置为 1 并将 ENABLE 设置为 0,则编译失败,因为没有函数 fn 接受参数 D。将 ENABLE 设置为 1 将阻止该错误的发生。

但是,如果我将 USE_DIRECT 设置为 0 并将 ENABLE 设置为 0,它将失败并显示不同的错误消息,但对于相同的情况,没有采用参数 Dfn。但是,将 ENABLE 设置为 1 将导致所有 4 个函数调用都失败。

为方便起见,这里是在线编译器中的代码:http://goo.gl/CQcXHr

谁能解释这里发生了什么以及为什么?

这看起来可能与Alias templates used in SFINAE lead to a hard error有关,但也没有人回答。

作为参考,以下是 g++ 生成的错误:

main.cpp: In instantiation of 'struct enable_if_is_base_of<A, collection2>':                                                                   
main.cpp:45:53:   required by substitution of 'template<class X> typename enable_if_is_base_of<X, collection2>::type fn(X) [with X = A]'       
main.cpp:54:8:   required from here                                                                                                            
main.cpp:34:82: error: no type named 'type' in 'struct std::enable_if<false, A>'                                                               
  typedef typename std::enable_if<std::is_base_of<X, COLLECTION>::value, X>::type type;                                                        
                                                                                  ^                                                            
main.cpp: In instantiation of 'struct enable_if_is_base_of<B, collection2>':                                                                   
main.cpp:45:53:   required by substitution of 'template<class X> typename enable_if_is_base_of<X, collection2>::type fn(X) [with X = B]'       
main.cpp:55:8:   required from here                                                                                                            
main.cpp:34:82: error: no type named 'type' in 'struct std::enable_if<false, B>'                                                               
main.cpp: In instantiation of 'struct enable_if_is_base_of<C, collection2>':                                                                   
main.cpp:45:53:   required by substitution of 'template<class X> typename enable_if_is_base_of<X, collection2>::type fn(X) [with X = C]'       
main.cpp:56:8:   required from here                                                                                                            
main.cpp:34:82: error: no type named 'type' in 'struct std::enable_if<false, C>'                                                               
main.cpp: In instantiation of 'struct enable_if_is_base_of<D, collection1>':                                                                   
main.cpp:38:53:   required by substitution of 'template<class X> typename enable_if_is_base_of<X, collection1>::type fn(X) [with X = D]'       
main.cpp:57:8:   required from here                                                                                                            
main.cpp:34:82: error: no type named 'type' in 'struct std::enable_if<false, D>'                                                               

【问题讨论】:

  • 呃,在你的间接情况下,顾名思义,错误不在直接上下文中,所以这是一个真正的错误......

标签: c++ c++11 templates sfinae enable-if


【解决方案1】:

只有在直接上下文中发生的替换可能会导致推理失败:

§14.8.2 [temp.deduct]/p8

只有在函数类型的直接上下文中的无效类型和表达式和 其模板参数类型可能会导致扣减失败。 [ 注意:替换类型的评估 和表达式可能会导致副作用例如类模板特化的实例化和/或 函数模板特化、隐式定义函数的生成等。这样的副作用是 不在“直接上下文”中,并可能导致程序格式错误。结束说明 ]

fn 的签名需要完整声明 enable_if_is_base 的专业化才能存在:

§14.7.1 [temp.inst]/p1

除非类模板特化已显式实例化 (14.7.2) 或显式特化 (14.7.3), 当在上下文中引用特化时,类模板特化被隐式实例化 需要完全定义的对象类型或当类类型的完整性影响语义时 程序。

编译器无法生成以下的特化:

template<typename X, typename COLLECTION>
struct enable_if_is_base_of
{
    static const int value = std::is_base_of<X, COLLECTION>::value;
    typedef typename std::enable_if<value, X>::type type;
};

因为如果value 的计算结果为false,则typename std::enable_if&lt;value, X&gt;::type 的替换会导致类型丢失,而这不在直接上下文中。

【讨论】:

  • 我很确定这就是我正在寻找的答案。我只是很难理解它。 o.O
  • 我现在在@BenVoigt 的帮助下理解了这意味着什么。虽然它引用了标准,但我会将您的标准标记为答案。
【解决方案2】:

正如已经回答的那样,您的 SFINAE 计划无法运行。但是,您可以使用以下不太漂亮的解决方案来实现您可能想要的:

#include <algorithm>
#include <type_traits>
#include <iostream>

class A{};
class B{};
class C{};
class D{};
class collection1 : A, B, C {};
class collection2 : D {};

template<typename X, class Enable = void>
struct enable_if_is_base_of;

template<typename X>
struct enable_if_is_base_of<X, typename std::enable_if<std::is_base_of<X, collection1>::value>::type> {
  static X fn(X x) {
    (void) x;
    std::cout << "collection1" << std::endl;
    return X();
  }
};

template<typename X>
struct enable_if_is_base_of<X, typename std::enable_if<std::is_base_of<X, collection2>::value>::type> {
  static X fn(X x) {
    (void) x;
    std::cout << "collection2" << std::endl;
    return X();
  }
};

int main() {
  enable_if_is_base_of<A>::fn(A());
  enable_if_is_base_of<B>::fn(B());
  enable_if_is_base_of<C>::fn(C());
  enable_if_is_base_of<D>::fn(D());
}

LIVE DEMO

【讨论】:

  • 是的,这实际上是走错路了。我试图简化代码的阅读。这实际上使这更难阅读,并使函数成为成员函数而不是纯函数。不过这很有趣。
【解决方案3】:

问题是如果类型不存在,typedef没有被跳过,这是一个错误。

解决方案:没有 typedef

template<typename X, typename COLLECTION>
struct enable_if_is_base_of : std::enable_if<std::is_base_of<X, COLLECTION>::value, X>
{};

现在type 成员存在(通过继承)当且仅当它存在于enable_if 实例化中。

【讨论】:

  • 我喜欢这个解决方案。这很简单。我只是不明白为什么该类型不存在是一个问题。我认为这就是重点。如果没有类型,则替换失败。替换失败不是错误,所以继续。
  • 哦,我明白了。 std::is_base_of&lt;X, COLLECTION&gt;::value 的计算结果为 nothing,因此导致 typedef type 格式错误,从而导致类不存在。因为它不存在,所以它不能有一个名为 type 的成员。继承它可以使代码在两种上下文中都形成良好的格式(无论它是否是基础),因此可以应用 SFINAE。
  • @Adrian:不完全是。 is_base_oftrue_typefalse_type,两者都定义了 value。这很好,因为 SFINAE 也不会出现在基类列表中。您最终继承自 std::enable_if&lt;false&gt;,它没有名为 type 的成员,完全符合您的需要。 SFINAE 仅适用于在重载解决期间从候选列表中删除模板函数。它不适用于 typedef、基类列表等。
  • §14.8.2 [temp.deduct]/p8 指的是什么。对吗?
  • @Adrian:是的,这是不同的说法。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-16
  • 2017-08-22
相关资源
最近更新 更多