【问题标题】:A compile time way to determine the least expensive argument type一种确定成本最低的参数类型的编译时方法
【发布时间】:2020-03-08 09:36:21
【问题描述】:

我有一个看起来像这样的模板

template <typename T> class Foo
{
public:
    Foo(const T& t) : _t(t) {}
private:
    const T _t;
};

在参数类型像 bool 或 char 这样微不足道的情况下,是否有一种精明的模板元编程方法可以避免使用 const 引用?喜欢:

Foo(stl::smarter_argument<T>::type t) : _t(t) {}

【问题讨论】:

  • 我不会担心,如果函数很小,编译器会内联它,引用甚至不存在。如果函数很大,将整数包装到引用中的微小成本将是微不足道的
  • 我会更担心完美转发,然后避免引用小数据类型。我猜在大多数情况下,通过 r 值引用传递可以优化为按值传递。
  • 要记住的一点,答案中没有指出:你正在做的事情会打败隐含的演绎指南。如果您关心为 Foo 工作的类模板参数推导,您应该记住编写显式推导指南。

标签: c++ stl


【解决方案1】:

我认为正确的类型特征是is_scalar。这将按如下方式工作:

template<class T, class = void>
struct smarter_argument{
    using type = const T&;
};

template<class T>
struct smarter_argument<T, std::enable_if_t<std::is_scalar_v<T>>> {
    using type = T;
};

编辑:

以上内容还是有点老套,感谢@HolyBlackCat 提醒我这个更简洁的版本:

template<class T>
using smarter_argument_t = std::conditional_t<std::is_scalar_v<T>, T, const T&>;

【讨论】:

  • @TarekDakhran 标量包括非基本的指针和枚举,应按 IMO 值传递。
  • 我不熟悉 class= void 语法。这是否意味着它可以是任何东西,因为它被忽略了?
  • = void 表示它的默认类型为 void,因此使用 smarter_argument&lt;T&gt; 实际上是 smarter_argument&lt;T, void&gt;。因为我们不需要它,所以我为这个参数留下了一个名字,因此class = void 没有名字。重要的是,std::enable_if_t 在启用的情况下也必须为空,以匹配默认类型。
  • 可以简化为template &lt;typename T&gt; using smarter_argument = std::conditional_t&lt;std::is_scalar_v&lt;T&gt;, T, const T &amp;&gt;;
  • std::conditional_tstd::enable_if_t 可以通过使用 C++20 概念大大简化。 See this post.
【解决方案2】:

我建议使用sizeof(size_t)(或sizeof(ptrdiff_t))返回与您的机器相关的“典型”大小,希望这种大小的任何变量都适合寄存器。在这种情况下,您可以安全地按值传递它。此外,正如 @n314159 所建议的(请参阅本文末尾的 cmets),确保变量也是 trivialy_copyable 很有用。

这是一个 C++17 演示:

#include <array>
#include <ccomplex>
#include <iostream>
#include <type_traits>

template <typename T>
struct maybe_ref
{
  using type = std::conditional_t<sizeof(T) <= sizeof(size_t) and
                                  std::is_trivially_copyable_v<T>, T, const T&>;
};

template <typename T>
using maybe_ref_t = typename maybe_ref<T>::type;

template <typename T>
class Foo
{
 public:
  Foo(maybe_ref_t<T> t) : _t(t)
  {
    std::cout << "is reference ? " << std::boolalpha 
              << std::is_reference_v<decltype(t)> << std::endl;
  }

private:
  const T _t;
};

int main()
{
                                                          // with my machine
  Foo<std::array<double, 1>> a{std::array<double, 1>{}};  // <- by value
  Foo<std::array<double, 2>> b{std::array<double, 2>{}};  // <- by ref

  Foo<double>               c{double{}};                // <- by value
  Foo<std::complex<double>> d{std::complex<double>{}};  // <- by ref
}

【讨论】:

  • 请注意,没有“机器的指针大小”之类的东西。 Run for example this: struct Foo { void bar(){ }; int i; }; std::cout &lt;&lt; sizeof(&amp;Foo::i) &lt;&lt; std::endl; //prints 8 std::cout &lt;&lt; sizeof(&amp;Foo::bar) &lt;&lt; std::endl; //prints 16
  • @BlueTune 有趣,感谢您的评论。另请参阅stackoverflow.com/a/6751914/2001017,如您的示例所示:指针和函数指针可能具有不同的大小。甚至不同的指针也可能有不同的大小。这个想法是获得机器的“典型”尺寸。我已经用 sizeof(size_t) 替换了模棱两可的 sizeof(void*)
  • 您可能还想检查T 是否可以轻松复制。例如,在我的平台上,共享指针的大小仅为size_t 的两倍,并且可以只用一个指针来实现,将其缩小到相同的大小。但是您肯定希望通过 const ref 而不是按值获取 shared_ptr。
  • @n314159 是的,这将是一个改进。你可以在我的回答中包含你的想法吗?
【解决方案3】:

我会使用 C++20 关键字 requires。就这样:

#include <iostream>

template<typename T>
class Foo
{
public:
    Foo(T t) requires std::is_scalar_v<T>: _t{t} { std::cout << "is scalar" <<std::endl; }
    Foo(const T& t) requires (not std::is_scalar_v<T>): _t{t} { std::cout << "is not scalar" <<std::endl;}
private:
    const T _t;
};

class cls {};

int main() 
{
    Foo{true};
    Foo{'d'};
    Foo{3.14159};
    cls c;
    Foo{c};

    return 0;
}

You can run the code online 查看以下输出:

is scalar
is scalar
is scalar
is not scalar

【讨论】:

  • 有趣。将 const auto& 用于构造函数参数有什么好处?
  • @cppguy:很高兴你问这个问题。如果我将参数“const auto& t”替换为“const T& t”,则代码将无法编译。错误显示为“...... 'Foo' 模板参数的模棱两可推论......”。也许你能找出原因?
  • @cppguy:我们的讨论导致了我提出的一个问题。你可以找到它here
  • 概念在这里是多余的,而且比其他选项更难阅读。
  • @S.S. Anne:恕我直言,使用 C++20 概念绝不是矫枉过正。它很优雅。恕我直言,到目前为止我看到的替代方案更难阅读,因为使用了嵌套模板。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-09-23
  • 1970-01-01
  • 1970-01-01
  • 2013-06-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多