Una forma de tiempo de compilación para determinar el tipo de argumento menos costoso

15

Tengo una plantilla que se ve así

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

¿Existe una forma inteligente de metaprogramación de plantillas para evitar el uso de una referencia constante en los casos en que el tipo de argumento es trivial como un bool o char? me gusta:

Foo(stl::smarter_argument<T>::type t) : _t(t) {}
cppguy
fuente
1
No me preocuparía si la función es pequeña, el compilador la alineará y la referencia ni siquiera existirá. Si la función es grande, el pequeño costo de incluir un número entero en una referencia será insignificante
Alan Birtles
1
Me preocuparía más el reenvío perfecto y luego evitar referencias sobre tipos de datos pequeños. Supongo que pasar por referencia de valor r puede optimizarse para pasar por valor en la mayoría de los casos.
Súper
Algo a tener en cuenta, no señalado en las respuestas: lo que está haciendo derrotará a las guías de deducción implícita. Debes recordar escribir una guía de deducción explícita si te interesa que funcione la deducción de argumentos de plantilla de clase Foo.
Brian

Respuestas:

13

Creo que el rasgo de tipo correcto es is_scalar. Esto funcionaría de la siguiente manera:

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:

Lo anterior sigue siendo un poco anticuado, gracias @HolyBlackCat por recordarme esta versión más concisa:

template<class T>
using smarter_argument_t = std::conditional_t<std::is_scalar_v<T>, T, const T&>;
n314159
fuente
no is_fundamentalfuncionaria tambien?
Tarek Dakhran
2
El escalar @TarekDakhran incluye punteros y enumeraciones que no son fundamentales, que deben pasarse por valor IMO.
LF
No estoy familiarizado con la sintaxis class = void. ¿Eso significa que puede ser cualquier cosa porque se ignora?
cppguy
1
= voidsignifica que tiene un tipo predeterminado que es nulo, por lo que usar smarter_argument<T>es realmente smarter_argument<T, void>. Dejé un nombre para este argumento, ya que no lo necesitamos, por lo tanto, class = voidsin un nombre. Es importante que, std::enable_if_ten caso de que esté habilitado, también se anule para que coincida con el tipo predeterminado.
n314159
2
Se puede simplificar a template <typename T> using smarter_argument = std::conditional_t<std::is_scalar_v<T>, T, const T &>;.
HolyBlackCat
3

Sugeriría usar sizeof(size_t)(o sizeof(ptrdiff_t)) que devuelve un tamaño "típico" relacionado con su máquina con la esperanza de que cualquier variable de este tamaño encaje en un registro. En ese caso, puede pasarlo de forma segura por valor. Además, como lo sugiere @ n314159 (ver comentarios al final de esta publicación), es útil asegurarse de que la variable también lo sea trivialy_copyable.

Aquí hay una demostración de 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
fuente
Tenga en cuenta que no existe el "tamaño del puntero de su máquina". Ejecute, por ejemplo, esto : 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 Interesante, gracias por el comentario. Consulte también stackoverflow.com/a/6751914/2001017 como muestra su ejemplo: los punteros y los punteros de función pueden tener diferentes tamaños. Incluso diferentes punteros pueden tener diferentes tamaños. La idea era obtener el tamaño "típico" de la máquina. He reemplazado el tamaño ambiguo de (void *) por sizeof (size_t)
Picaud Vincent
1
@Picaud tal vez quiera usarlo en <=lugar de ==, en la mayoría de las máquinas, su código actual toma un charejemplo como referencia si lo veo bien.
n314159
2
También es posible que desee comprobar si Tse puede copiar trivialmente. Por ejemplo, un puntero compartido es solo dos veces el tamaño de size_tmi plataforma y se puede implementar con un solo puntero, reduciéndolo al mismo tamaño. Pero definitivamente desea tomar shared_ptr por const ref y no por valor.
n314159
@ n314159 sí, eso sería una mejora. ¿Estás bien si incluyes tu idea en mi respuesta?
Picaud Vincent
2

Haría uso de la palabra clave C ++ 20 requires. Así:

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

Puede ejecutar el código en línea para ver el siguiente resultado:

is scalar
is scalar
is scalar
is not scalar
BlueTune
fuente
Interesante. ¿Hay algún beneficio en usar const auto & para el argumento del constructor?
cppguy
@cppguy: Me alegra que hagas esa pregunta. Si reemplazo el argumento "const auto & t" con "const T & t", el código no se compilará. El error dice "... deducción ambigua para argumentos de plantilla de 'Foo' ...". ¿Quizás puedas averiguar por qué?
BlueTune
1
@cppguy: Nuestra discusión resultó en una pregunta que planteé. Lo puedes encontrar aquí .
BlueTune
1
Aquí, los conceptos son excesivos y mucho más difíciles de leer que la alternativa.
SS Anne
1
@SS Anne: en mi humilde opinión, usar conceptos de C ++ 20 nunca es una exageración. Es simplemente elegante. En mi humilde opinión, las alternativas que he visto hasta ahora son más difíciles de leer, debido al uso de plantillas anidadas.
BlueTune