确定最便宜参数类型的编译时方法


15

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

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

有一种精明的模板元编程方法可以避免在参数类型像布尔值或char这样琐碎的情况下使用const引用吗?喜欢:

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

1
我不会担心,如果函数很小,编译器将内联它,甚至引用也将不存在。如果函数很大,则将整数包装到引用中的微不足道的费用将微不足道
Alan Birtles

1
我会更担心完美的转发,然后再避免引用小数据类型。我猜想在大多数情况下,按r值引用传递可以优化为按值传递。
超级

需要记住的事情,而不是在答案中指出的:您正在做的事情会打败隐含的推论指南。如果您关心类模板参数的推导工作,则应该记得写一个明确的推导指南Foo
Brian

Answers:


13

我认为正确的类型特征是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&>;

is_fundamental行不通吗?
Tarek Dakhran

2
@TarekDakhran标量包含不是基本的指针和枚举,应通过值IMO传递。
LF

我对class = void语法不熟悉。这是否意味着它可以是任何东西,因为它被忽略了?
cppguy

1
= void表示它具有默认类型void,因此使用smarter_argument<T>is实际上smarter_argument<T, void>。我为该参数保留了名称,因为我们不需要它,因此class = void没有名称。重要的是std::enable_if_t,如果启用它,则必须也无效以使其与默认类型匹配。
n314159

2
可以简化为template <typename T> using smarter_argument = std::conditional_t<std::is_scalar_v<T>, T, const T &>;
HolyBlackCat

3

我建议使用sizeof(size_t)(或sizeof(ptrdiff_t))返回与您的计算机有关的“典型”大小,希望该大小的任何变量都适合寄存器。在这种情况下,您可以安全地按值传递它。此外,如@ n314159所建议(请参阅本文末尾的评论),确保变量也为很有用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
}

请注意,没有“机器的指针大小”之类的东西。例如运行struct Foo { void bar(){ }; int i; }; std::cout << sizeof(&Foo::i) << std::endl; //prints 8 std::cout << sizeof(&Foo::bar) << std::endl; //prints 16
BlueTune

@BlueTune有趣,感谢您的评论。另请参见stackoverflow.com/a/6751914/2001017,如您的示例所示:指针和函数指针的大小可能不同。甚至不同的指针也可能具有不同的大小。想法是获得机器的“典型”尺寸。我用sizeof(size_t)代替了模糊的sizeof(void *)
Picaud Vincent

1
@Picaud也许您想使用<=而不是==,在大多数机器上char,如果我看到正确的话,当前的代码将作为参考。
n314159

2
您可能还需要检查可T复制的内容。例如,共享指针的大小仅为size_t我平台上的两倍,并且只需一个指针即可实现,并将其减小到相同的大小。但是,您绝对希望使用const ref而不是值来使用shared_ptr。
n314159

@ n314159是的,这将是一个改进。如果您的想法包含在我的回答中,您可以吗?
Picaud Vincent

2

我会使用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;
}

您可以在线运行代码以查看以下输出:

is scalar
is scalar
is scalar
is not scalar

有趣。使用const auto&作为构造函数参数有好处吗?
cppguy

@cppguy:很高兴你问这个问题。如果我将参数“ const auto&t”替换为“ const T&t”,则代码将无法编译。错误显示为“ ...'Foo'的模板参数的模棱两可的推论...”。也许您能找出原因?
BlueTune

1
@cppguy:我们的讨论导致我提出了一个问题。你可以在这里找到它。
BlueTune

1
这里的概念过于刻板,比其他概念更难阅读。
SS安妮

1
@SS Anne:恕我直言,使用C ++ 20概念永远不过分。真是优雅。恕我直言,到目前为止,我看到的替代方法更难阅读,因为使用了嵌套模板。
BlueTune
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.