Eine Methode zur Kompilierungszeit, um den kostengünstigsten Argumenttyp zu bestimmen


15

Ich habe eine Vorlage, die so aussieht

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

Gibt es eine clevere Methode zur Metaprogrammierung von Vorlagen, um die Verwendung einer const-Referenz in Fällen zu vermeiden, in denen der Argumenttyp trivial ist wie ein Bool oder ein Zeichen? mögen:

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

1
Ich würde mir darüber keine Sorgen machen, wenn die Funktion klein ist, wird der Compiler sie einbinden und die Referenz wird nicht einmal existieren. Wenn die Funktion groß ist, sind die winzigen Kosten für das Umschließen einer Ganzzahl in eine Referenz unbedeutend
Alan Birtles

1
Ich würde mich mehr um eine perfekte Weiterleitung kümmern als um Verweise auf kleine Datentypen. Ich vermute, dass das Übergeben der R-Wert-Referenz in den meisten Fällen auf das Übergeben des Werts optimiert werden kann.
Super

Etwas zu beachten, das in den Antworten nicht erwähnt wird: Was Sie tun, besiegt die impliziten Abzugsrichtlinien. Sie sollten daran denken, einen expliziten Abzugsleitfaden zu schreiben, wenn Sie daran interessiert sind, dass der Abzug von Klassenvorlagenargumenten funktioniert Foo.
Brian

Antworten:


13

Ich denke, das richtige Merkmal ist is_scalar. Dies würde wie folgt funktionieren:

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

Bearbeiten:

Das Obige ist immer noch ein bisschen altmodisch, danke @HolyBlackCat, dass du mich an diese knappere Version erinnert hast:

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

würde auch nicht is_fundamentalfunktionieren?
Tarek Dakhran

2
Der @ TarekDakhran-Skalar enthält Zeiger und Aufzählungen, die nicht grundlegend sind und als Wert IMO übergeben werden sollten.
LF

Ich bin mit der Syntax class = void nicht vertraut. Bedeutet das, dass es alles sein kann, weil es ignoriert wird?
cppguy

1
= voidbedeutet, dass es einen Standardtyp hat, der ungültig smarter_argument<T>ist smarter_argument<T, void>. Ich habe einen Namen für dieses Argument weggelassen, da wir ihn nicht brauchen, daher class = voidohne Namen. Es ist wichtig, dass der std::enable_if_tFall, dass er aktiviert ist, ebenfalls ungültig ist, damit er dem Standardtyp entspricht.
n314159

2
Kann vereinfacht werden template <typename T> using smarter_argument = std::conditional_t<std::is_scalar_v<T>, T, const T &>;.
HolyBlackCat

3

Ich würde vorschlagen, sizeof(size_t)(oder sizeof(ptrdiff_t)) zu verwenden, die eine "typische" Größe für Ihre Maschine zurückgibt, in der Hoffnung, dass jede Variable dieser Größe in ein Register passt. In diesem Fall können Sie es sicher als Wert übergeben. Darüber hinaus ist es, wie von @ n314159 vorgeschlagen (siehe Kommentare am Ende dieses Beitrags), sicherzustellen, dass die Variable auch vorhanden ist trivialy_copyable.

Hier ist eine C ++ 17-Demo:

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

Beachten Sie, dass es keine "Zeigergröße Ihres Computers" gibt. Führen Sie zum Beispielstruct Foo { void bar(){ }; int i; }; std::cout << sizeof(&Foo::i) << std::endl; //prints 8 std::cout << sizeof(&Foo::bar) << std::endl; //prints 16
Folgendes aus

@BlueTune Interessant, danke für den Kommentar. Siehe auch stackoverflow.com/a/6751914/2001017, wie Ihr Beispiel zeigt: Zeiger und Funktionszeiger können unterschiedliche Größen haben. Sogar unterschiedliche Zeiger können unterschiedliche Größen haben. Die Idee war, eine "typische" Größe der Maschine zu erhalten. Ich habe die mehrdeutige Größe von (void *) durch sizeof (size_t) ersetzt
Picaud Vincent

1
@Picaud Vielleicht möchten Sie <=stattdessen verwenden ==, auf den meisten Computern wird Ihr aktueller Code charbeispielsweise als Referenz verwendet, wenn ich das richtig sehe.
n314159

2
Möglicherweise möchten Sie auch überprüfen, ob Tsie trivial kopierbar sind. Zum Beispiel ist ein gemeinsam genutzter Zeiger nur doppelt so groß wie size_tauf meiner Plattform und kann mit nur einem Zeiger implementiert werden, sodass er dieselbe Größe hat. Aber Sie möchten auf jeden Fall den shared_ptr nach const ref und nicht nach value nehmen.
n314159

@ n314159 ja das wäre eine verbesserung. Sind Sie in Ordnung, wenn Sie Ihre Idee in meine Antwort aufnehmen?
Picaud Vincent

2

Ich würde das Schlüsselwort C ++ 20 verwenden requires. Genau so:

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

Sie können den Code online ausführen , um die folgende Ausgabe anzuzeigen:

is scalar
is scalar
is scalar
is not scalar

Interessant. Gibt es einen Vorteil bei der Verwendung von const auto & für das Konstruktorargument?
cppguy

@cppguy: Ich bin froh, dass du diese Frage stellst. Wenn ich das Argument "const auto & t" durch "const T & t" ersetze, wird der Code nicht kompiliert. Der Fehler lautet "... mehrdeutiger Abzug für Vorlagenargumente von 'Foo' ...". Vielleicht können Sie herausfinden, warum?
BlueTune

1
@cppguy: Unsere Diskussion führte zu einer Frage, die ich gestellt habe. Sie finden es hier .
BlueTune

1
Konzepte sind hier übertrieben und wesentlich schwerer zu lesen als die Alternative.
SS Anne

1
@SS Anne: IMHO mit C ++ 20-Konzepten ist nie ein Overkill. Es ist einfach elegant. IMHO sind die Alternativen, die ich bisher gesehen habe, schwerer zu lesen, da verschachtelte Vorlagen verwendet werden.
BlueTune
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.