使用C ++ 11类型特征来提供备用内联实现

时间:2016-11-04 06:00:43

标签: c++ c++11 templates exception-handling typetraits

在模板化代码中使用traits时,以下代码模式是否合理,其中两个替代实现始终可编译?

阅读代码似乎比其他恶作剧有条件地编译更清楚(但也许我对这些恶作剧不够熟悉)。

 <DataGrid.InputBindings>
            <MouseBinding Command="{Binding DataContext.NavCommand,RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
        MouseAction="LeftDoubleClick"
       CommandParameter="historyimage"/>
        </DataGrid.InputBindings>

(增加的复杂性部分是为了提供强大的异常保证。)

我知道这段代码将工作,但是期望编译器消除常量错误的分支并为noexcept情况进行内联等(比其他情况简单得多)是否合理?我希望在noexcept情况下能够像只用第一个块作为正文编写方法一样有效(反之亦然,尽管我不太担心复杂的情况)。

如果这不是正确的方法,有人可以请教我推荐的语法吗?

6 个答案:

答案 0 :(得分:21)

  

[...]期望编译器消除常量错误的分支并对noexcept情况进行内联等(比其他情况简单得多)是否合理?

可能是,但我不会依赖它,因为你无法控制它。

如果您要删除if/else,可以 sfinae 返回类型并清除noexcept限定符。
举个例子:

template<typename T>
class X {
    template<typename U = T>
    std::enable_if_t<std::is_nothrow_copy_constructible<T>::value>
    do_something()
    noexcept(true)
    {}

    template<typename U = T>
    std::enable_if_t<not std::is_nothrow_copy_constructible<T>::value>
    do_something()
    noexcept(false)
    {}
};

缺点是你现在有两个成员函数模板 不确定它是否符合您的要求。

如果您被允许使用C ++ 17中的功能,那么if constexpr可能就是您的选择,您不必再在两个成员函数中破坏您的方法。

另一种方法可以基于标记调度您类型的noexcept
举个例子:

template<typename T>
class X {
    void do_something(std::true_type)
    noexcept(true)
    {}

    void do_something(std::false_type)
    noexcept(false)
    {}

    void do_something()
    noexcept(do_something(std::is_nothrow_copy_constructible<T>{}))
    { do_something(std::is_nothrow_copy_constructible<T>{}); }
};

我知道, sfinae 不是动词,但是对某些事情听起来非常好。

答案 1 :(得分:13)

  

期望编译器消除常量错误分支并为noexcept案例进行内联等是否合理?

是。话虽如此,必须实例化常量 - 错误分支,这可能会或可能不会导致编译器实例化一堆您不需要的符号(然后您需要依赖链接器来删除它们)。

我仍然会选择SFINAE恶作剧(实际上是标签调度),这可以在C ++ 11中轻松完成。

template<typename T>
class X
{
    void do_something() noexcept(std::is_nothrow_copy_constructible<T>::value)
    {
        do_something_impl(std::is_nothrow_copy_constructible<T>() ); 
    }

    void do_something_impl( std::true_type /* nothrow_copy_constructible */ )
    {
        // some short code that assumes T's copy constructor won't throw
    }

    void do_something_impl( std::false_type /* nothrow_copy_constructible */)
    {
        // some longer code with try/catch blocks and more complexity
    }

    // many other methods
};

如果您要在所有其他方法中检查nothrow_copy_constructor,您可以考虑专门化整个班级:

template<typename T, class = std::is_nothrow_copy_constructible_t<T> >
class X
{
   //throw copy-ctor implementation
};

template<typename T>
class X<T, std::true_type>
{
   // noexcept copy-ctor implementation
};

答案 2 :(得分:8)

  

期望编译器消除常量假分支是否合理?

是的,消除死代码是最简单的优化之一。

  

...并为noexcept案例进行内联等工作?

我的第一个冲动是回答“不,你不能依赖它,因为它取决于内联通道在优化流程中相对于死代码消除步骤的位置”。< / p>

但是经过更多反思,我无法理解为什么在内联步骤之前和之后,在足够高的优化级别上成熟的编译器不会执行死代码消除。所以这个期望也应该是合理的。

然而,关于优化的猜测绝不是肯定的。转到简单的实现并获得正确运行的代码。然后测量其性能并检查您的假设是否正确。如果他们不是 - 重新设计你的情况的实施将不会花费比从一开始就走下保证路径更多的时间。

答案 3 :(得分:6)

每个成熟的编译器都会执行死代码消除。每个成熟的编译器都检测到常量分支,而死代码则检测到其他分支。

您可以使用十几个模板参数创建一个函数,该函数在其正文中使用简单的if检查,并且无疑地查看结果 - 这不会有问题。

如果您执行创建static变量或thread_local或实例化符号等操作,则更难以消除这些内容。

内联是一个小问题,因为编译器倾向于在某个时候放弃内联;代码越复杂,编译器在内联之前就越有可能放弃它。

在C ++ 17中,您可以将if升级到constexpr版本。但是在C ++ 14和11中,你的代码会做得很好。它比其他选择更简单,更容易阅读。

它有点脆弱,但如果它破坏了它通常在编译时以嘈杂的方式这样做。

答案 4 :(得分:1)

  

但是期望编译器消除常量错误分支

是合理的

没有。所有分支都将由编译器进行评估。您可以尝试使用c ++ 17中的if constexpr

你想要达到的目标是SFINAE。

答案 5 :(得分:1)

您可以尝试自己实施constexpr_if。 c ++ 11解决方案可能如下所示:

#include <iostream>
#include <type_traits>

template <bool V>
struct constexpr_if {
   template <class Lambda, class... Args>
   static int then(Lambda lambda, Args... args) {
      return 0;
   }
};

template <>
struct constexpr_if<true> {
   template <class Lambda, class... Args>
   static auto then(Lambda lambda, Args... args) -> decltype(lambda(args...)) {
       return lambda(args...);
   }

   static int then(...) {
       return 0;
   }
};

struct B {
   B() {}
   B(const B &) noexcept {}
   void do_something() {
      std::cout << "B::do_something()" << std::endl;
   }
};

struct C {
   C() {}
   C(const C &) noexcept {}
   void do_something_else() {
      std::cout << "C::do_something_else()" << std::endl;
   }
};

struct D {
   D() {}
   D(const D &) throw(int) {}
   void do_something_else() {
      std::cout << "D::do_something_else()" << std::endl;
   }
};

template <class T>
struct X {
   void do_something() {
      T t;
      constexpr_if<std::is_nothrow_copy_constructible<T>::value>::then([](B &b) {
         b.do_something();
      }, t);
      constexpr_if<std::is_nothrow_copy_constructible<T>::value>::then([](C &c) {
         c.do_something_else();
      }, t);
      constexpr_if<!std::is_nothrow_copy_constructible<T>::value>::then([](D &d) {
         d.do_something_else();
      }, t);
   }
};

int main() {
   X<B> x;
   x.do_something();
   X<C> xx;
   xx.do_something();
   X<D> xxx;
   xxx.do_something();
}

输出:

B::do_something()
C::do_something_else()
D::do_something_else()