【问题标题】:How to use sfinae for selecting constructors?如何使用 sfinae 选择构造函数?
【发布时间】:2013-01-14 05:15:39
【问题描述】:

在模板元编程中,可以在返回类型上使用SFINAE来选择某个模板成员函数,即

template<int N> struct A {
  int sum() const noexcept
  { return _sum<N-1>(); }
private:
  int _data[N];
  template<int I> typename std::enable_if< I,int>::type _sum() const noexcept
  { return _sum<I-1>() + _data[I]; }
  template<int I> typename std::enable_if<!I,int>::type _sum() const noexcept
  { return _data[I]; }
};

但是,这不适用于构造函数。假设,我要声明构造函数

template<int N> struct A {
   /* ... */
   template<int otherN>
   explicit(A<otherN> const&); // only sensible if otherN >= N
};

但不允许 otherN &lt; N 使用它。

那么,可以在这里使用 SFINAE 吗?我只对允许自动模板参数推导的解决方案感兴趣,所以

A<4> a4{};
A<5> a5{};
A<6> a6{a4};  // doesn't compile
A<3> a3{a5};  // compiles and automatically finds the correct constructor

注意:这是一个非常简化的示例,其中 SFINAE 可能是多余的,static_assert 可能就足够了。但是,我想知道我是否可以改用 SFINAE。

【问题讨论】:

  • 我们真的应该得到一个核心语言功能来取代这种 SFINAE 滥用,我认为一厢情愿......
  • 请更正代码,让它按照你的意图去做
  • @JohannesSchaub-litb 我没有得到您的编辑/评论。 A&lt;6&gt; a6{a4}; 是(意味着)对我想使用 SFINAE 的类复制构造函数的调用。所以参数必须是一个类型,而不是A&lt;4&gt;::operator() 的结果,就像你的A&lt;6&gt; a6(a4()) 一样。
  • @Walter 问题是 a4 和 a5 是函数。现在它已经修复了。

标签: c++ c++11 constructor sfinae


【解决方案1】:

您可以向模板添加默认类型参数:

template <int otherN, typename = typename std::enable_if<otherN >= N>::type>
explicit A(A<otherN> const &);

【讨论】:

  • 请注意,如果您有两个具有不同条件的构造函数,这将无法正常工作。 (如果你没有重载,你可能应该使用 static_assert,但已经在问题中处理了)
  • @KerrekSB:我认为,因为第一个 typename 阐明 enable_if::type 必须是 typename。
  • @KerrekSB:这是可选的吗?真的吗?好吧,从上下文中应该清楚必须遵循类型名,但我不知道 C++ 实际上允许在此处删除第二个类型名。你确定吗?也许您已经尝试过使用更宽容的编译器,但实际上并没有进行正确的两阶段查找?
  • @R.MartinhoFernandes:不过,静态断言是不同的。 enable_if 的重点是为 std::is_constructible&lt;A&lt;N&gt;, A&lt;M&gt;&gt;::value 获得正确的行为。
  • @Walter : C++03 确实如此,但 C++11 也允许它们用于函数模板。
【解决方案2】:

触发 SFINAE 的方法有很多种,enable_if 只是其中一种。 首先:

Wats 是 std::enable_if 吗?

就是这样:

template<bool, class T=void> enable_if{ typedef T type; };
template<class T> enable_if<false,T> {};
template<bool b, class T=void> using enable_if_t = typename enable_f<b,T>::type;

这个想法是让typename enable_if&lt;false&gt;::type 成为一个错误,因此跳过任何包含它的模板声明。

那么这个触发功能怎么选择呢?

禁用功能

这个想法使声明在某些部分是错误的:

按返回类型

template<class Type>
std::enable_if_t<cond<Type>::value,Return_type> function(Type);

通过实际参数

template<class Type>
return_type function(Type param, std::enable_if_t<cond<Type>::value,int> =0) 

通过模板参数

template<class Type, 
    std::enable_if_t<cond<Type>::value,int> =0> //note the space between > and =
return_type function(Type param) 

选择功能

您可以使用以下技巧参数化不同的替代方案:

tempplate<int N> struct ord: ord<N-1>{};
struct ord<0> {};

template<class T, std::enable_if<condition3, int> =0>
retval func(ord<3>, T param) { ... }

template<class T, std::enable_if<condition2, int> =0>
retval func(ord<2>, T param) { ... }

template<class T, std::enable_if<condition1, int> =0>
retval func(ord<1>, T param) { ... }

template<class T> // default one
retval func(ord<0>, T param) { ... }

// THIS WILL BE THE FUCNTION YOU'LL CALL
template<class T>
retval func(T param) { return func(ord<9>{},param); } //any "more than 3 value"

如果满足condition3,这将调用first/second/third/fourth函数,而不是满足condition2而不是condition1

其他 SFINAE 触发器

编写编译时条件可以是显式特化的问题,也可以是未评估的表达式成功/失败的问题:

例如:

template<class T, class = void>
struct is_vector: std::false_type {};
template<class X>
struct is_vector<vector<X> >:: std::true_type {};

所以is_vector&lt;int&gt;::valuefalseis_vecttor&lt;vector&lt;int&gt; &gt;::valuetrue

或者,通过内省,比如

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

template<class T>
struct is_container<T, decltype(
  std::begin(std::declval<T>()),
  std::end(std::declval<T>()),
  std::size(std::declval<T>()),
  void(0))>: std::true_type {};

如果给定X x,那么is_container&lt;X&gt;::value 将是true,您可以编译std::begin(x) 等。

诀窍是decltype(...) 实际上是void, 运算符丢弃以前的表达式)只有当所有子表达式都是可编译的。


甚至可以有许多其他选择。希望在这一切之间你能找到一些有用的东西。

【讨论】:

    【解决方案3】:

    在 C++11 中,您可以使用默认模板参数:

    template <int otherN, class = typename std::enable_if<otherN >= N>::type>
    explicit A(A<otherN> const &);
    

    但是,如果您的编译器还不支持默认模板参数,或者您需要多个重载,那么您可以使用这样的默认函数参数:

    template <int otherN>
    explicit A(A<otherN> const &, typename std::enable_if<otherN >= N>::type* = 0);
    

    【讨论】:

      【解决方案4】:

      在大多数情况下,可接受的答案是好的,但如果存在两个具有不同条件的构造函数重载,则会失败。我也在寻找这种情况下的解决方案。

      是的:接受的解决方案有效,但不适用于两个替代构造函数,例如,

      template <int otherN, typename = typename std::enable_if<otherN == 1>::type>
      explicit A(A<otherN> const &);
      
      template <int otherN, typename = typename std::enable_if<otherN != 1>::type>
      explicit A(A<otherN> const &);
      

      因为,正如this page 中所述,

      一个常见的错误是声明两个仅在默认模板参数上有所不同的函数模板。这是非法的,因为默认模板参数不是函数模板签名的一部分,并且用相同的签名声明两个不同的函数模板是非法的。

      正如在同一页面中提出的那样,您可以通过将 SFINAE 修改签名来解决此问题,如下所示为值(不是类型)模板参数的类型

      template <int otherN, typename std::enable_if<otherN == 1, bool>::type = true>
      explicit A(A<otherN> const &);
      
      template <int otherN, typename std::enable_if<otherN != 1, bool>::type = true>
      explicit A(A<otherN> const &);
      

      【讨论】:

      • template&lt;typename U = T,typename enable_if_t&lt;is_same_v&lt;U,opcode&gt; ,bool&gt; =true&gt; constexpr arg(U&amp;&amp;v) : value{v}, name{"opcode"}{}中说error: expected a qualified name after 'typename'
      • @kyb - 如果没有完整的示例,很难说出有用的话,但是......也许在enable_if_tis_same_v 之前使用std::
      • 我正在使用命名空间标准。看来我应该问一个不同的问题。
      • @kyb - 是的:请提供一个带有完整示例的新问题。
      【解决方案5】:

      在 C++20 中,您可以使用 requires 关键字

      使用 C++20,您可以摆脱 SFINAE。

      requires 关键字是 enable_if 的简单替代品!

      请注意,otherN == N 的情况是一种特殊情况,因为它属于 默认复制 ctor,所以如果您想要照顾你必须单独实现它:

      template<int N> struct A {
         A() {}    
      
         // handle the case of otherN == N with copy ctor
         explicit A(A<N> const& other) { /* ... */ }
      
         // handle the case of otherN > N, see the requires below
         template<int otherN> requires (otherN > N)
         explicit A(A<otherN> const& other) { /* ... */ }
      
         // handle the case of otherN < N, can add requires or not
         template<int otherN>
         explicit A(A<otherN> const& other) { /* ... */ }
      };
      

      requires 子句得到一个constant expression,其计算结果为truefalse,从而决定是否在重载决议中考虑此方法,如果 requires 子句为真,则该方法优于另一个没有 requires 子句的,因为它更专业。

      代码:https://godbolt.org/z/RD6pcE

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-06-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多