Cara kompilasi waktu untuk menentukan jenis argumen yang paling murah

15

Saya memiliki template yang terlihat seperti ini

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

Apakah ada cara pemrograman cerdas template untuk menghindari menggunakan referensi const dalam kasus di mana jenis argumen sepele seperti bool atau char? Suka:

Foo(stl::smarter_argument<T>::type t) : _t(t) {}
cppguy
sumber
1
Saya tidak akan khawatir tentang hal itu, jika fungsinya kecil kompiler akan sebaris itu dan bahkan referensi tidak akan ada. Jika fungsinya besar, biaya kecil untuk membungkus integer ke dalam referensi tidak akan signifikan
Alan Birtles
1
Saya akan lebih khawatir tentang penerusan yang sempurna kemudian menghindari referensi pada tipe data kecil. Saya menduga bahwa lulus dengan referensi r-nilai dapat dioptimalkan untuk lulus-oleh-nilai dalam banyak kasus.
Super
Sesuatu yang perlu diingat, tidak ditunjukkan dalam jawaban: apa yang Anda lakukan akan mengalahkan panduan deduksi implisit. Anda harus ingat untuk menulis panduan deduksi eksplisit jika Anda peduli tentang deduksi argumen templat kelas Foo.
Brian

Jawaban:

13

Saya pikir sifat tipe yang tepat adalah is_scalar. Ini akan berfungsi sebagai berikut:

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

Edit:

Di atas masih agak kuno, terima kasih @HolyBlackCat untuk mengingatkan saya pada versi yang lebih singkat ini:

template<class T>
using smarter_argument_t = std::conditional_t<std::is_scalar_v<T>, T, const T&>;
n314159
sumber
tidak akan is_fundamentalbekerja juga?
Tarek Dakhran
2
Skalar @TarekDakhran termasuk pointer dan enum yang tidak mendasar, yang harus diteruskan oleh nilai IMO.
LF
Saya tidak terbiasa dengan sintaks class = void. Apakah itu berarti itu bisa apa saja karena diabaikan?
cppguy
1
= voidberarti memiliki tipe default yang batal, jadi menggunakan smarter_argument<T>sebenarnya smarter_argument<T, void>. Saya tidak menyebutkan nama untuk argumen ini karena kami tidak membutuhkannya, karenanya class = voidtanpa nama. Penting bahwa std::enable_if_tjika diaktifkan juga harus batal agar cocok dengan jenis default.
n314159
2
Dapat disederhanakan menjadi template <typename T> using smarter_argument = std::conditional_t<std::is_scalar_v<T>, T, const T &>;.
HolyBlackCat
3

Saya akan menyarankan untuk menggunakan sizeof(size_t)(atau sizeof(ptrdiff_t)) yang mengembalikan ukuran "khas" yang terkait dengan mesin Anda dengan harapan bahwa setiap variabel ukuran ini cocok dengan register. Dalam hal ini Anda dapat dengan aman menyampaikannya berdasarkan nilai. Selain itu, seperti yang disarankan oleh @ n314159 (lihat komentar di akhir posting ini) berguna untuk memastikan bahwa variabelnya juga trivialy_copyable.

Berikut ini adalah demo 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
}
Picaud Vincent
sumber
Perhatikan bahwa tidak ada yang namanya "ukuran pointer mesin Anda". Jalankan misalnya ini : 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 Menarik, terima kasih atas komentarnya. Lihat juga stackoverflow.com/a/6751914/2001017 sebagai contoh Anda menunjukkan: pointer dan pointer fungsi mungkin memiliki ukuran yang berbeda. Bahkan pointer yang berbeda mungkin memiliki ukuran yang berbeda pula. Idenya adalah untuk mendapatkan ukuran mesin "tipikal". Saya telah mengganti sizeof ambigu (void *) dengan sizeof (size_t)
Picaud Vincent
1
@Picaud mungkin Anda ingin menggunakan <=bukan ==, pada kebanyakan mesin kode Anda saat ini mengambil charcontoh dengan referensi jika saya melihatnya benar.
n314159
2
Anda mungkin juga ingin memeriksa apakah Tdapat disalin sepele. Sebagai contoh, sebuah pointer yang dibagikan hanya berukuran dua kali lipat dari size_tpada plattform saya dan itu dapat diimplementasikan hanya dengan satu pointer, sehingga turun ke ukuran yang sama. Tapi Anda pasti ingin mengambil shared_ptr oleh const ref dan bukan dengan nilai.
n314159
@ n314159 ya itu akan menjadi perbaikan. Apakah Anda baik-baik saja jika memasukkan ide Anda dalam jawaban saya?
Picaud Vincent
2

Saya akan menggunakan kata kunci C ++ 20 requires. Seperti itu:

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

Anda dapat menjalankan kode secara online untuk melihat output berikut:

is scalar
is scalar
is scalar
is not scalar
BlueTune
sumber
Menarik. Apakah ada manfaat menggunakan const & auto untuk argumen konstruktor?
cppguy
@ cppguy: Saya senang Anda menanyakan pertanyaan itu. Jika saya mengganti argumen "const auto & t" dengan "const T & t" kode tidak akan dikompilasi. Kesalahan tersebut berbunyi "... pengurangan ambigu untuk argumen templat 'Foo' ...". Mungkin Anda bisa mencari tahu mengapa?
BlueTune
1
@ cppguy: Diskusi kami menghasilkan pertanyaan yang saya ajukan. Anda dapat menemukannya di sini .
BlueTune
1
Konsep di sini berlebihan dan jauh lebih sulit dibaca daripada alternatifnya.
SS Anne
1
@ SS Anne: IMHO menggunakan konsep C ++ 20 tidak pernah berlebihan. Itu hanya elegan. IMHO alternatif yang saya lihat sejauh ini lebih sulit untuk dibaca, karena penggunaan templat bersarang.
BlueTune