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

Using C++11 type traits to provide alternate inline implementations

在两种替代实现始终可编译的模板代码中使用特征时,以下代码模式是否合理?

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

template<typename T>
class X
{
    void do_something() noexcept(std::is_nothrow_copy_constructible<T>::value)
    {
        if (std::is_nothrow_copy_constructible<T>::value)
        {
            // some short code that assumes T's copy constructor won't throw
        }
        else
        {
            // some longer code with try/catch blocks and more complexity
        }
    }

    // many other methods
};

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

我知道这段代码会 工作 ,但是期望编译器消除常量假分支并为 noexcept 情况进行内联等是否合理(比另一种情况)?我希望在 noexcept 情况下能像只用第一个块作为主体编写方法一样高效(反之亦然,尽管我不太担心复杂的情况)。

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

but is it reasonable to expect the compiler to eliminate the constant-false branches

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

你想要实现的是 SFINAE。

[...] is it reasonable to expect the compiler to eliminate the constant-false branches and do inlining etc for the noexcept case (where much simpler than the other case)?

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


如果要删除 if/else,可以 sfinae return 类型并清除 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 不是动词,但是 to sfinae something 听起来很好。

您可以尝试自己实施 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()

Is it reasonable to expect the compiler to eliminate the constant-false branches?

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

... and do inlining etc for the noexcept case?

我的第一反应是回答"No, you can't rely on that, since it depends on where the inlining pass sits in the optimization flow relative to the dead code elimination step"

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

然而,关于优化的猜测从来都不是一件确定的事情。寻求简单的实现并获得正确运行的代码。然后测量其性能并检查您的假设是否正确。如果他们不是 - 根据您的情况重新设计实施将不会比您从一开始就沿着有保证的路径花费更多的时间。

is it reasonable to expect the compiler to eliminate the constant-false branches and do inlining etc for the noexcept case [...]?

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

我仍然会使用 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,您可以考虑专门化整个 class:

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
};

每个成熟的编译器都会消除死代码。每个成熟的编译器都会检测常量分支,并对另一个分支进行死编码。

您可以创建一个带有十几个模板参数的函数,这些模板参数在其主体中使用天真的 if 检查并查看生成的假设 - 不会有问题。

如果您执行诸如创建 static 变量或 thread_local 或实例化符号之类的操作,这些都更难消除。

内联有点棘手,因为编译器往往会在某个时候放弃内联;代码越复杂,编译器在内联之前放弃的可能性就越大。

在 C++17 中,您可以将 if 升级到 constexpr 版本。但在 C++14 和 11 中,您的代码就可以正常工作。它比替代品更简单易读。

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