Un moyen de compilation pour déterminer le type d'argument le moins cher

15

J'ai un modèle qui ressemble à ceci

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

Existe-t-il un moyen de métaprogrammation de modèle avisé pour éviter d'utiliser une référence const dans les cas où le type d'argument est trivial comme un bool ou un char? comme:

Foo(stl::smarter_argument<T>::type t) : _t(t) {}
cppguy
la source
1
Je ne m'en inquiéterais pas, si la fonction est petite, le compilateur l'inline et la référence n'existera même pas. Si la fonction est grande, le coût minime d'encapsuler un entier dans une référence sera insignifiant
Alan Birtles
1
Je m'inquiéterais davantage de la transmission parfaite que de l'évitement des références sur les petits types de données. Je suppose que le passage par la référence de valeur r peut être optimisé pour passer par la valeur dans la plupart des cas.
Super
Quelque chose à garder à l'esprit, non souligné dans les réponses: ce que vous faites vaincra les guides de déduction implicite. Vous devez vous rappeler d'écrire un guide de déduction explicite si vous vous souciez de la déduction des arguments de modèle de classe Foo.
Brian

Réponses:

13

Je pense que le bon trait de type est is_scalar. Cela fonctionnerait comme suit:

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

Éditer:

Ce qui précède est encore un peu old-school, merci @HolyBlackCat de me rappeler cette version plus laconique:

template<class T>
using smarter_argument_t = std::conditional_t<std::is_scalar_v<T>, T, const T&>;
n314159
la source
ne is_fundamentalfonctionnerait pas aussi?
Tarek Dakhran
2
@TarekDakhran scalar inclut des pointeurs et des énumérations qui ne sont pas fondamentaux, qui doivent être passés par la valeur IMO.
LF
Je ne connais pas la syntaxe class = void. Est-ce à dire que cela peut être quelque chose parce qu'il est ignoré?
cppguy
1
= voidsignifie qu'il a un type par défaut qui est vide, donc l'utilisation smarter_argument<T>est en fait smarter_argument<T, void>. J'ai laissé un nom pour cet argument car nous n'en avons pas besoin, donc class = voidsans nom. Il est important que le std::enable_if_tcas où il est activé doit également être annulé pour qu'il corresponde au type par défaut.
n314159
2
Peut être simplifié en template <typename T> using smarter_argument = std::conditional_t<std::is_scalar_v<T>, T, const T &>;.
HolyBlackCat
3

Je suggérerais d'utiliser sizeof(size_t)(ou sizeof(ptrdiff_t)) qui renvoie une taille "typique" liée à votre machine avec l'espoir que toute variable de cette taille rentre dans un registre. Dans ce cas, vous pouvez le transmettre en toute sécurité par valeur. De plus, comme suggéré par @ n314159 (voir les commentaires à la fin de ce post), il est utile de s'assurer que la variable l'est également trivialy_copyable.

Voici une démo 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
la source
Notez qu'il n'existe pas de "taille du pointeur de votre machine". Exécutez par exemple ceci : 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 Intéressant, merci pour le commentaire. Voir également stackoverflow.com/a/6751914/2001017 comme le montre votre exemple: les pointeurs et les pointeurs de fonction peuvent avoir des tailles différentes. Même des pointeurs différents peuvent avoir des tailles différentes. L'idée était d'obtenir une taille "typique" de la machine. J'ai remplacé le sizeof (void *) ambigu par sizeof (size_t)
Picaud Vincent
1
@Picaud peut-être que vous souhaitez utiliser à la <=place de ==, sur la plupart des machines, votre code actuel prend un charexemple par référence si je vois bien.
n314159
2
Vous voudrez peut-être également vérifier qu'il Test trivialement copiable. Par exemple, un pointeur partagé ne fait que deux fois la taille de size_tsur ma plate-forme et il peut être implémenté avec un seul pointeur, le ramenant à la même taille. Mais vous voulez certainement prendre le shared_ptr par const ref et non par valeur.
n314159
@ n314159 oui, ce serait une amélioration. Êtes-vous d'accord si vous incluez votre idée dans ma réponse?
Picaud Vincent
2

J'utiliserais le mot-clé C ++ 20 requires. Juste comme ça:

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

Vous pouvez exécuter le code en ligne pour voir la sortie suivante:

is scalar
is scalar
is scalar
is not scalar
BlueTune
la source
Intéressant. Y a-t-il un avantage à utiliser const auto & pour l'argument constructeur?
cppguy
@cppguy: Je suis content que vous posiez cette question. Si je remplace l'argument "const auto & t" par "const T & t" le code ne sera pas compilé. L'erreur indique "... déduction ambiguë pour les arguments de modèle de 'Foo' ...". Peut-être pouvez-vous découvrir pourquoi?
BlueTune
1
@cppguy: Notre discussion a abouti à une question que j'ai posée. Vous pouvez le trouver ici .
BlueTune
1
Les concepts sont exagérés ici et beaucoup plus difficiles à lire que l'alternative.
SS Anne
1
@SS Anne: À mon humble avis, l'utilisation des concepts C ++ 20 n'est jamais une exagération. C'est juste élégant. À mon humble avis, les alternatives que j'ai vues jusqu'à présent sont plus difficiles à lire, car l'utilisation de modèles imbriqués.
BlueTune