Sposób kompilacji w celu ustalenia najtańszego typu argumentu

15

Mam szablon, który wygląda tak

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

Czy istnieje sprytny sposób metaprogramowania szablonów, aby uniknąć używania stałej referencji w przypadkach, gdy typ argumentu jest trywialny, jak bool lub char? lubić:

Foo(stl::smarter_argument<T>::type t) : _t(t) {}
cppguy
źródło
1
Nie martwiłbym się tym, jeśli funkcja jest mała, kompilator wstawi ją, a referencja nawet nie będzie istnieć. Jeśli funkcja jest duża, niewielki koszt zawinięcia liczby całkowitej w odwołanie będzie nieznaczny
Alan Birtles
1
Martwiłbym się bardziej o idealne przekazywanie niż unikanie odniesień do małych typów danych. Zgaduję, że przekazywanie przez referencję wartości r może być zoptymalizowane do przekazywania wartości w większości przypadków.
super
O czym należy pamiętać, a nie wskazano w odpowiedziach: to, co robisz, pokona ukryte wskazówki dedukcyjne. Powinieneś pamiętać o napisaniu wyraźnego przewodnika po dedukcji, jeśli zależy ci na dedukcji argumentów na szablonie klasy Foo.
Brian

Odpowiedzi:

13

Myślę, że właściwą cechą jest typ is_scalar. To działałoby w następujący sposób:

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

Edytować:

Powyższe jest wciąż trochę oldschoolowe, dzięki @HolyBlackCat za przypomnienie mi o tej bardziej zwięzłej wersji:

template<class T>
using smarter_argument_t = std::conditional_t<std::is_scalar_v<T>, T, const T&>;
n314159
źródło
też by nie is_fundamentaldziałał?
Tarek Dakhran
2
Skalar @TarekDakhran zawiera wskaźniki i wyliczenia, które nie są fundamentalne, które powinny być przekazywane przez wartość IMO.
LF
Nie znam składni class = void. Czy to znaczy, że może to być cokolwiek, ponieważ jest ignorowane?
cppguy
1
= voidoznacza, że ​​ma domyślny typ, który jest nieważny, więc użycie smarter_argument<T>jest w rzeczywistości smarter_argument<T, void>. Odrzuciłem nazwę dla tego argumentu, ponieważ nie jest nam potrzebna, stąd class = voidbez nazwy. Ważne jest, aby std::enable_if_tw przypadku, gdy jest on włączony, również musi być nieważny, aby pasował do domyślnego typu.
n314159
2
Można to uprościć template <typename T> using smarter_argument = std::conditional_t<std::is_scalar_v<T>, T, const T &>;.
HolyBlackCat
3

Sugerowałbym użyć sizeof(size_t)(lub sizeof(ptrdiff_t)), który zwraca „typowy” rozmiar związany z twoim komputerem z nadzieją, że dowolna zmienna tego rozmiaru zmieści się w rejestrze. W takim przypadku możesz bezpiecznie przekazać wartość. Ponadto, jak sugeruje @ n314159 (patrz komentarze na końcu tego postu), warto upewnić się, że zmienna jest również trivialy_copyable.

Oto 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
źródło
Zauważ, że nie ma czegoś takiego jak „rozmiar wskaźnika twojego komputera”. Uruchom na przykład : 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 Ciekawe, dziękuję za komentarz. Zobacz także stackoverflow.com/a/6751914/2001017, jak pokazuje twój przykład: wskaźniki i wskaźniki funkcji mogą mieć różne rozmiary. Nawet różne wskaźniki mogą mieć różne rozmiary. Pomysł polegał na uzyskaniu „typowego” rozmiaru maszyny. Niejednoznaczny sizeof (void *) zastąpiłem sizeof (size_t)
Picaud Vincent
1
@Picaud może chcesz użyć <=zamiast tego ==, na większości komputerów twój kod przyjmuje charprzykładowo przez odniesienie, jeśli dobrze to widzę.
n314159
2
Możesz także sprawdzić, czy można Tgo w prosty sposób skopiować. Na przykład wspólny wskaźnik jest tylko dwa razy większy niż size_tna mojej platformie i można go zaimplementować za pomocą tylko jednego wskaźnika, dzięki czemu osiągnie ten sam rozmiar. Ale zdecydowanie chcesz wziąć parametr shared_ptr według stałej referencji, a nie wartości.
n314159
@ n314159 tak, to byłaby poprawa. Czy wszystko w porządku, jeśli uwzględnij swój pomysł w odpowiedzi?
Picaud Vincent
2

Chciałbym skorzystać ze słowa kluczowego C ++ 20 requires. Właśnie tak:

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

Możesz uruchomić kod online, aby zobaczyć następujące dane wyjściowe:

is scalar
is scalar
is scalar
is not scalar
BlueTune
źródło
Ciekawy. Czy jest korzyść z użycia const auto & jako argumentu konstruktora?
cppguy
@cppguy: Cieszę się, że zadajesz to pytanie. Jeśli zamienię argument „const auto & t” na „const T & t”, kod nie zostanie skompilowany. Błąd brzmi „... niejednoznaczne odjęcie argumentów szablonu„ Foo ”...”. Może dowiesz się dlaczego?
BlueTune
1
@cppguy: Nasza dyskusja zaowocowała postawionym pytaniem. Możesz go znaleźć tutaj .
BlueTune
1
Pojęcia są tutaj przesadą i znacznie trudniejsze do odczytania niż alternatywa.
SS Anne
1
@SS Anne: IMHO korzystające z koncepcji C ++ 20 nigdy nie jest przesadą. To jest po prostu eleganckie. IMHO alternatywy, które widziałem do tej pory, są trudniejsze do odczytania, ponieważ użycie zagnieżdżonych szablonów.
BlueTune