最も安価な引数の型を決定するコンパイル時の方法


15

このようなテンプレートがあります

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

引数の型がブールや文字のように取るに足らない場合にconst参照の使用を回避するための精通したテンプレートメタプログラミング方法はありますか?お気に入り:

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

1
関数が小さい場合、コンパイラーはそれをインライン化し、参照も存在しないので、私はそれを心配しません。関数が大きい場合、整数を参照にラップするための小さなコストは重要ではありません
Alan Birtles

1
私は完全な転送についてもっと心配し、小さなデータ型の参照を避けます。ほとんどの場合、r値の参照による受け渡しは、値による受け渡しに最適化できると思います。
スーパー

答えで指摘されていない、覚えておくべきこと:あなたがやっていることは、暗黙の演繹ガイドを無効にします。で機能するクラステンプレート引数の控除が気になる場合は、明示的な控除ガイドを作成することを忘れないでくださいFoo
ブライアン

回答:


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>実際にはを使用し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(void *)をsizeof(size_t)に置き換えました
Picaud Vincent

1
@Picaud <===、の代わりに使用したい場合があります。ほとんどのマシンでは、現在のコードは、charたとえば、私がその権利を参照している場合、参照によって取得します。
n314159

2
T簡単にコピーできるかどうかを確認することもできます。たとえば、共有ポインターはsize_t私のプラットフォームの2倍のサイズであり、1つのポインターだけで実装して同じサイズにすることができます。ただし、shared_ptrを値ではなくconst refで取得することは間違いありません。
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アン:C ++ 20の概念を使用したIMHOは、やり過ぎではありません。まさにエレガントです。ネストされたテンプレートを使用しているため、これまでに見た代替案は読みにくくなっています。
BlueTune
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.