【问题标题】:SFINAE check for operator+=SFINAE 检查运算符+=
【发布时间】:2014-10-01 10:16:56
【问题描述】:

如果缺少operator+=,我正在尝试从重载集中消除重载。

我知道如何检查T+T 是否合法:

template<typename T,
         typename CheckTplusT = decltype(std::declval<T>() + std::declval<T>())>
void foo(T a, T b, ...)
{
  a = a + b;
}

但这不适用于+=

template<typename T,
         typename CheckTplusT = decltype(std::declval<T>() += std::declval<T>())>
void foo(T a, T b, ...)
{
  a += b;
}

这是否可以通过在 decltype 中使用另一个表达式来解决,还是我需要另一个 SFINAE 构造?

我需要从重载集中消除它的原因是它与另一个重载冲突,该重载接受函子作为+= 的替代品。编译器为VS2013、gcc4.8

【问题讨论】:

  • 您致电foo 的哪种形式不起作用?
  • @PiotrS。 : 第二种形式不行。您不能在右值 std::declval&lt;T&gt;() 上调用 +=(非常量方法)。但是你可以在右值上调用 + 。比较 2+2 和 2+=2
  • @MSalters 如果+= 是一个方法,您可以在右值上调用它(除非该方法具有&amp; 限定符)。问题在于内置 += 和基本类型。
  • @dyp:我希望将此方法用于 short、float、double 和 std::complex&lt;&gt;
  • 虽然这个问题已经得到了很好的回答,但如果你有可用的 Boost,你也可以使用enable_if。 Boost.TypeTraits'boost::has_plus_assign 可以工作。这可能是首选的非 C++11 选项。

标签: c++ sfinae


【解决方案1】:

我会把第二种形式写成:

template<typename T>
auto foo(T a, T b, ...) -> decltype( a+=b, void() )
{
  a += b;
}

如果表达式a+=b 有效,则decltype(a+=b, void()) 的推导类型将只是void,否则将导致SFINAE。

好吧,即使是第一种形式,我也会使用尾随返回类型的方法。

【讨论】:

  • 已接受。由于其他原因(返回类型取决于函子参数),重载集的其余部分已经需要尾随返回类型,因此该解决方案实际上增加了不同重载之间的相似性。 @gexicide 的解决方案更接近我最初的实现,但这对未来的维护者没有好处。
  • 它应该无法编译。它是无效的,因为 A 没有 +=。(即使您在调用 Foo 时不使用临时变量)
  • @CashCow 好的,我又读了一遍 Q,我明白了。缺少operator+= 时编译应该会失败
  • @CashCow:这是完全可以接受的。如果您尝试foo(nullptr, nullptr),则可以抱怨foo 不能将两个空指针作为参数(逻辑上不正确)。
  • 我只想说这个解决方案非常漂亮,并且是我发现(到目前为止)唯一可以用作通用成员检测器的方法。 (其他解决方案不可避免地需要每次检查单独的 TMP 结构)
【解决方案2】:

您需要在+= 左侧有一个lvalue,但您的解决方案有一个xvalue。正如 dyp 在 cmets 中所述,您可以使用 declval&lt;T&amp;&gt; 获取左值。这很好用(刚刚测试过):

template<typename T,
         typename CheckTplusT = decltype(std::declval<T&>() += std::declval<T>())>
void foo(T a, T b, ...)
{
}

【讨论】:

  • 第二个declval 可能也必须使用T&amp;,具体取决于函数的详细信息(a += ba += move(b))。
  • 我知道std::declval&lt;T&amp;&gt;() 会起作用,但我仍然不喜欢这个解决方案。它看起来很丑陋。此外,如果 operator+= 采用非常量引用参数,则此解决方案将不起作用。尾随返回类型解决方案更清洁、更好且正确 ,因为程序员不必处理对象的值类别。让编译器来做吧。
  • @Nawaz:是的,但它最接近 OP 的方法。
  • 完成我的重载集后,我回到了这个解决方案。重载集包含另一个template&lt;typename T&gt; foo,当我使用@Nawaz 解决方案时,编译器正确地抱怨了具有不同返回类型的重新声明。这是第一阶段的问题。但是这个解决方案有不同的模板参数,这很好。
  • @MSalters:您能否发布在使用我的解决方案时出现重新声明问题的代码,但这个答案显示的方法没有出现任何此类问题?
【解决方案3】:

这个怎么样?是std::declval之前使用的方法。

template<typename T,
         typename CheckTplusT = decltype(*(T*)nullptr += std::declval<T>())>
void foo(T a, T b, ...)
{
  a += b;
  std::cout << "foo with +=" << std::endl;
}

【讨论】:

  • std::declval&lt;T&amp;&gt;() 更干净。
【解决方案4】:

添加这个 main() 函数:

int main()
{
    int x = 1, y = 2;
    foo( x, y );
}

这是编译器错误:

 main.cpp: In function int main():  main.cpp:15:15: error: no matching
 function for call to foo(int&, int&)
      foo( x, y );
            ^  main.cpp:15:15: note: candidate is:  
 main.cpp:7:6: note: template<class T, class CheckTplusT> void foo(T, T, ...)  void

 foo(T a, T b, ...)
   ^ main.cpp:7:6: note:   template argument deduction/substitution failed: 
    main.cpp:6:60: error:    
      using xvalue (rvalue reference) as lvalue
       typename CheckTplusT = decltype(std::declval<T>() += std::declval<T>())>

关键行是using xvalue (rvalue reference) as lvalue

这是declval的文档

此解决方法对我有用:

template<typename T,
     typename CheckTpluseqT = decltype(*std::declval<T*>() += *std::declval<T*>())>
void foo(T &a, T b, ...)
{
   a += b;
 }

int main()
{
   int a = 1, b = 2;
   foo( a, b );
   std::cout << a << std::endl;
}

输出 3

当然你也可以使用declval&lt;T&amp;&gt;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-09-21
    • 1970-01-01
    • 2017-12-27
    • 2011-11-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多