Un modo di compilazione per determinare il tipo di argomento meno costoso


15

Ho un modello che assomiglia a questo

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

Esiste un metodo esperto di metaprogrammazione per evitare l'uso di un riferimento const nei casi in cui il tipo di argomento è banale come un bool o un char? piace:

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

1
Non mi preoccuperei, se la funzione è piccola il compilatore la incorporerà e il riferimento non esisterà. Se la funzione è grande, il costo ridotto di un intero in un riferimento sarà insignificante
Alan Birtles

1
Mi preoccuperei di più di un perfetto inoltro e quindi di evitare riferimenti su piccoli tipi di dati. Immagino che il passaggio per riferimento di valore r possa essere ottimizzato per passare per valore nella maggior parte dei casi.
super

Qualcosa da tenere a mente, non sottolineato nelle risposte: ciò che stai facendo sconfiggerà le guide implicite alla deduzione. Dovresti ricordare di scrivere una guida esplicita alla detrazione se ti interessa che la deduzione degli argomenti del modello di classe funzioni Foo.
Brian

Risposte:


13

Penso che il tratto giusto sia is_scalar. Funzionerebbe come segue:

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

Modificare:

Quanto sopra è ancora un po 'vecchio stile, grazie @HolyBlackCat per avermi ricordato questa versione più concisa:

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

non is_fundamentalfunzionerebbe anche?
Tarek Dakhran,

2
Lo scalare di @TarekDakhran include puntatori ed enum che non sono fondamentali, che dovrebbero essere passati per valore IMO.
LF

Non ho familiarità con la sintassi class = void. Significa che può essere qualsiasi cosa perché viene ignorato?
cppguy

1
= voidsignifica che ha un tipo predefinito che è nullo, quindi l'utilizzo smarter_argument<T>è effettivamente smarter_argument<T, void>. Ho lasciato un nome a questo argomento poiché non ne abbiamo bisogno, quindi class = voidsenza un nome. È importante che anche std::enable_if_tnel caso in cui sia abilitato, deve essere nullo affinché corrisponda al tipo predefinito.
n314159

2
Può essere semplificato a template <typename T> using smarter_argument = std::conditional_t<std::is_scalar_v<T>, T, const T &>;.
HolyBlackCat

3

Suggerirei di usare sizeof(size_t)(o sizeof(ptrdiff_t)) che restituisca una dimensione "tipica" relativa alla tua macchina con la speranza che qualsiasi variabile di queste dimensioni si inserisca in un registro. In tal caso puoi tranquillamente passarlo per valore. Inoltre, come suggerito da @ n314159 (vedere i commenti alla fine di questo post) è utile assicurarsi che anche la variabile sia trivialy_copyable.

Ecco una demo di 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
}

Si noti che non esiste "la dimensione del puntatore della macchina". Esegui ad esempio questo : 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 Interessante, grazie per il commento. Vedi anche stackoverflow.com/a/6751914/2001017 come mostra l'esempio: i puntatori e i puntatori a funzioni possono avere dimensioni diverse. Anche puntatori diversi possono avere dimensioni diverse. L'idea era di ottenere le dimensioni "tipiche" della macchina. Ho sostituito l'ambigua dimensione di (void *) con dimensione di (size_t)
Picaud Vincent

1
@Picaud forse vuoi usare <=invece di ==, sulla maggior parte delle macchine il tuo codice attuale prende charad esempio un riferimento se lo vedo giusto.
n314159

2
Potresti anche voler verificare che Tsia banalmente copiabile. Ad esempio, un puntatore condiviso ha solo il doppio della dimensione della size_tmia piattaforma e può essere implementato con un solo puntatore, riducendolo alla stessa dimensione. Ma sicuramente vuoi prendere il common_ptr per const ref e non per valore.
n314159

@ n314159 sì, sarebbe un miglioramento. Stai bene se includi la tua idea nella mia risposta?
Picaud Vincent,

2

Vorrei utilizzare la parola chiave C ++ 20 requires. Proprio così:

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

È possibile eseguire il codice online per visualizzare il seguente output:

is scalar
is scalar
is scalar
is not scalar

Interessante. C'è un vantaggio nell'uso di const auto & per l'argomento del costruttore?
cppguy

@cppguy: sono contento che tu abbia fatto questa domanda. Se sostituisco l'argomento "const auto & t" con "const T & t" il codice non verrà compilato. L'errore riporta "... deduzione ambigua per argomenti modello di" Foo "...". Forse puoi scoprire perché?
BlueTune

1
@cppguy: la nostra discussione ha portato a una domanda che ho posto. Puoi trovarlo qui .
BlueTune

1
Concepts è eccessivo qui e sostanzialmente più difficile da leggere rispetto all'alternativa.
SS Anne

1
@SS Anne: IMHO che usa i concetti di C ++ 20 non è mai eccessivo. È semplicemente elegante. IMHO le alternative che ho visto finora sono più difficili da leggere, perché l'uso di modelli nidificati.
BlueTune
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.