Uma maneira de tempo de compilação para determinar o tipo de argumento mais barato

15

Eu tenho um modelo que se parece com isso

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

Existe uma maneira inteligente de metaprogramação de modelos para evitar o uso de uma referência const nos casos em que o tipo de argumento é trivial como um bool ou char? gostar:

Foo(stl::smarter_argument<T>::type t) : _t(t) {}
cppguy
fonte
11
Eu não me preocuparia com isso, se a função for pequena, o compilador incluirá a linha e a referência nem existirá. Se a função for grande, o pequeno custo de agrupar um número inteiro em uma referência será insignificante
Alan Birtles
11
Eu me preocuparia mais com o encaminhamento perfeito, evitando referências em tipos de dados pequenos. Suponho que a passagem por referência de valor-r possa ser otimizada para passagem por valor na maioria dos casos.
super
Algo a ter em mente, não indicado nas respostas: o que você está fazendo derrotará os guias implícitos de dedução. Lembre-se de escrever um guia de dedução explícito se você se preocupa com a dedução de argumentos do modelo de classe em que trabalha Foo.
Brian

Respostas:

13

Eu acho que o traço de tipo certo é is_scalar. Isso funcionaria da seguinte maneira:

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

Editar:

A descrição acima ainda é um pouco antiga, obrigado @HolyBlackCat por me lembrar desta versão mais concisa:

template<class T>
using smarter_argument_t = std::conditional_t<std::is_scalar_v<T>, T, const T&>;
n314159
fonte
não iria is_fundamentalfuncionar também?
Tarek Dakhran
2
O escalar do @TarekDakhran inclui ponteiros e enumerações que não são fundamentais, que devem ser passados ​​pelo valor IMO.
LF
Não estou familiarizado com a sintaxe class = void. Isso significa que pode ser qualquer coisa porque é ignorado?
cppguy 8/03
11
= voidsignifica que ele tem um tipo padrão que é nulo; portanto, usar smarter_argument<T>é realmente smarter_argument<T, void>. Deixei um nome para esse argumento, pois não precisamos dele, portanto, class = voidsem nome. É importante que o std::enable_if_tcaso de estar ativado também seja nulo para que ele corresponda ao tipo padrão.
n314159 8/03
2
Pode ser simplificado para template <typename T> using smarter_argument = std::conditional_t<std::is_scalar_v<T>, T, const T &>;.
HolyBlackCat 8/03
3

Eu sugeriria usar sizeof(size_t)(ou sizeof(ptrdiff_t)) que retorne um tamanho "típico" relacionado à sua máquina com a esperança de que qualquer variável desse tamanho se encaixe em um registro. Nesse caso, você pode passá-lo com segurança por valor. Além disso, como sugerido por @ n314159 (consulte os comentários no final deste post), é útil garantir que a variável também seja trivialy_copyable.

Aqui está uma demonstração do 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
fonte
Observe que não existe "o tamanho do ponteiro da sua máquina". Execute, por exemplo, o seguinte : 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, obrigado pelo comentário. Consulte também stackoverflow.com/a/6751914/2001017 como mostra o seu exemplo: ponteiros e ponteiros de função podem ter tamanhos diferentes. Mesmo ponteiros diferentes podem ter tamanhos diferentes. A idéia era obter o tamanho "típico" da máquina. Substituí o tamanho ambíguo sizeof (void *) por sizeof (size_t)
Picaud Vincent
11
@Picaud, talvez você queira usar , em <=vez de ==, na maioria das máquinas, o código atual usa, charpor exemplo, uma referência, se eu achar certo.
n314159 8/03
2
Você também pode querer verificar se Té trivialmente copiável. Por exemplo, um ponteiro compartilhado tem apenas o dobro do tamanho da size_tminha plataforma e pode ser implementado com apenas um ponteiro, reduzindo-o ao mesmo tamanho. Mas você definitivamente quer pegar o shared_ptr por const ref e não por valor.
n314159 8/03
@ n314159 sim, isso seria uma melhoria. Você está bem se incluir sua ideia na minha resposta?
Picaud Vincent
2

Eu usaria a palavra-chave C ++ 20 requires. Bem desse jeito:

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

Você pode executar o código online para ver a seguinte saída:

is scalar
is scalar
is scalar
is not scalar
BlueTune
fonte
Interessante. Existe um benefício em usar const auto & para o argumento do construtor?
cppguy 8/03
@cppguy: Fico feliz que você fez essa pergunta. Se eu substituir o argumento "const auto & t" por "const T & t", o código não será compilado. O erro diz "... dedução ambígua para argumentos de modelo de 'Foo' ...". Talvez você possa descobrir o porquê?
BlueTune 8/03
11
@cppguy: Nossa discussão resultou em uma pergunta que eu fiz. Você pode encontrá-lo aqui .
BlueTune 8/03
11
Conceitos é um exagero aqui e substancialmente mais difícil de ler do que a alternativa.
SS Anne
11
@ SS Anne: IMHO usando conceitos de C ++ 20 nunca é um exagero. É apenas elegante. IMHO as alternativas que eu vi até agora são mais difíceis de ler, porque o uso de modelos aninhados.
BlueTune