使用c++ 11类型特征来提供替代的内联实现

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

本文关键字:实现 c++ 类型 特征 使用      更新时间:2023-10-16

当在模板化代码中使用特征时,以下代码模式是否合理,其中两个替代实现总是可编译的?

阅读代码似乎比做其他有条件编译的把戏更清楚(但可能我只是不太熟悉那些把戏)。

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

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

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

如果这不是正确的方法,谁能告诉我推荐的语法?

[…期望编译器消除constant-false分支并为noexcept情况(比其他情况简单得多)进行内联等是合理的吗?

有可能,但我不相信,因为你无法控制它。


如果要删除if/else,可以指定返回类型并清除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听起来太好了。

期望编译器消除constant-false分支并为noexcept情况做内联等是否合理[…]?

是的。也就是说,必须实例化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,你可以考虑专门化整个类:

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

期望编译器消除constant-false分支是否合理?

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

…在noexcept情况下做内联等吗?

我的第一个冲动是回答"不,你不能依赖它,因为它取决于内联传递相对于死代码消除步骤在优化流程中的位置"

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

然而,猜测关于优化从来都不是一件确定的事情。追求简单的实现并得到功能正确的代码。然后衡量它的表现,并检查你的假设是否正确。如果没有,那么根据您的情况重新设计实现并不会比从一开始就沿着保证的路径花费更多的时间。

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

您可以创建一个带有十几个模板参数的函数,它在函数体中使用朴素的if检查,并假设地查看结果——这不会有问题。

如果您创建static变量或thread_local或实例化符号,这些都很难消除。

内联有点棘手,因为编译器倾向于在某些时候放弃内联;代码越复杂,编译器就越有可能在内联之前放弃。

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

有点脆弱,但是如果它在编译时被破坏了,它通常会以一种嘈杂的方式发生。

但是期望编译器消除constant-false分支

是否合理?

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

你想要实现的是SFINAE

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