9

Ich bin spezialisiert std::common_type für meinen Typen. Ich habe folgende Spezialisierung definiert:Klasse Template-Spezialisierung, die alle Versionen von konst/volatilen Qualifikationen akzeptiert und & vs &&

common_type<my_type, my_type> 

Und alles ist gut. Dann kommt jemand vorbei und ruft std::common_type<my_type, my_type &> an. Die Standardversion verhält sich genauso, wenn Sie eine Referenz im Gegensatz zu einer Referenz übergeben (wie sie std::decay auf den Typen aufruft). Es wird jedoch nicht auf die Nicht-Referenzversion von std::common_type verwiesen, die ich richtig arbeiten müsste. Gibt es einen besseren Weg, als so etwas zu tun zu haben (Weglassen rvalue Verweis der Einfachheit halber auf const):

common_type<my_type, my_type> 
common_type<my_type, my_type &> 
common_type<my_type, my_type const &> 
common_type<my_type, my_type volatile &> 
common_type<my_type, my_type const volatile &> 
common_type<my_type, my_type &&> 
common_type<my_type, my_type volatile &&> 
common_type<my_type &, my_type> 
common_type<my_type const &, my_type> 
common_type<my_type volatile &, my_type> 
common_type<my_type const volatile &, my_type> 
common_type<my_type &&, my_type> 
common_type<my_type volatile &&, my_type> 
common_type<my_type &, my_type &> 
common_type<my_type &, my_type const &> 
common_type<my_type &, my_type volatile &> 
... 

Sicherlich gibt es einen besseren Weg? Nach meiner Schätzung ist, dass 49 mögliche Versionen, wenn wir const && und const volatile &&

Hinweis ignorieren: my_type ist eigentlich eine Klasse Vorlage selbst, so dass die Spezialisierung sieht tatsächlich eher wie

template<intmax_t lhs_min, intmax_t lhs_max, intmax_t rhs_min, intmax_t rhs_max> 
class common_type<my_type<lhs_min, lhs_max>, my_type<rhs_min, rhs_max>> 

Wo ist das Ergebnis my_type<min(lhs_min, rhs_min), max(lhs_max, rhs_max)>

wäre die Lösung recht einfach sein, wenn ich die volle Kontrolle über die primären Vorlagendefinitionen habe, aber ich kann natürlich nicht std::common_type ändern.

+0

Haben Sie einen Blick darauf geworfen, was die Standardbibliothek von 'std :: common_type' bedeutet? – Casey

+0

Die Implementierung hat hier nicht viel Freiheit. Sie muss 'std :: decay' aufrufen (oder eine Implementierung, die davon nicht unterscheidbar ist). Und tatsächlich, das ist, was Gcc 4.8.2 tut –

+0

Es ist irgendwie impliziert, aber nur um sicherzustellen: Sie können nicht anders als 'std :: common_type', d. H. Ändern Sie die" Call-Site "? Und Sie können SFINAE-geschützte Konvertierungsoperator-Templates nicht definieren, um '' my_type '" natürlich "zu machen? – dyp

Antwort

2

Soweit ich weiß, müssen Sie nicht beide Seiten der binären common_type vollständig spezialisieren. Dies ermöglicht die Reduzierung der Spezialisierung auf 12 für eine Seite. Wenn Sie nur einen gemeinsamen Typ zwischen den Spezialisierungen my_type und my_type benötigen, reicht es aus, sich auf einer Seite zu spezialisieren. Andernfalls müssten Sie sie auf der rechten Seite klonen und 24 Spezialisierungen erhalten.

struct my_type; 
struct unique_t; 

#include <type_traits> 

template<class L, class R, class = void> 
struct mytype_common_type 
{ 
    // not many specializations are required here, 
    // as you can use std::decay and don't have to use "Exact Matches" 
    using type = unique_t; 
}; 

namespace std 
{ 
    template<class T> struct common_type<my_type, T> 
    : mytype_common_type<my_type, T> {}; 
    template<class T> struct common_type<my_type const, T> 
    : mytype_common_type<my_type, T> {}; 
    template<class T> struct common_type<my_type volatile, T> 
    : mytype_common_type<my_type, T> {}; 
    template<class T> struct common_type<my_type const volatile, T> 
    : mytype_common_type<my_type, T> {}; 

    template<class T> struct common_type<my_type&, T> 
    : mytype_common_type<my_type, T> {}; 
    template<class T> struct common_type<my_type const&, T> 
    : mytype_common_type<my_type, T> {}; 
    template<class T> struct common_type<my_type volatile&, T> 
    : mytype_common_type<my_type, T> {}; 
    template<class T> struct common_type<my_type const volatile&, T> 
    : mytype_common_type<my_type, T> {}; 

    template<class T> struct common_type<my_type&&, T> 
    : mytype_common_type<my_type, T> {}; 
    template<class T> struct common_type<my_type const&&, T> 
    : mytype_common_type<my_type, T> {}; 
    template<class T> struct common_type<my_type volatile&&, T> 
    : mytype_common_type<my_type, T> {}; 
    template<class T> struct common_type<my_type const volatile&&, T> 
    : mytype_common_type<my_type, T> {}; 
} 

template<class T> 
using Decay = typename std::decay<T>::type; 

int main() 
{ 
    static_assert(std::is_same<unique_t, 
        std::common_type<my_type const volatile&&, int>::type 
        >{}, "!"); 
} 
+0

Ich sehe nicht, wie ich es auf der richtigen Seite tatsächlich duplizieren könnte. Würde das nicht zu einer mehrdeutigen Spezialisierung führen, wenn ich 'std :: common_type ' und 'std :: common_type ' definiere? –

+1

@DavidStone Sie haben recht, wenn Sie gerade die Spezialisierungen 'common_type ' und 'common_type ' verwendet haben, wäre das mehrdeutig. Sie können das jedoch beheben, indem Sie für die Spezialisierungen auf der rechten Seite 'enable_if' verwenden:' template struct common_type :: type, mein_typ> {}, mein_typ> Geben Sie ein: mytype_common_type {}; ' – dyp

1

Ich würde vorschlagen, die Spezialisierung ohne die CV- und ref- Qualifier schreiben, und mit einem Wrapper um std::common_type<>, wie folgt aus:

template <typename T> 
using decay_t = typename std::decay<T>::type; 

/* Our wrapper which passes decayed types to std::common_type<>. */ 
template <typename... T> 
using CommonType = std::common_type<decay_t<T>...>; 

namespace std { 

    /* Specialization for my_type<>. */ 
    template <intmax_t lhs_min, intmax_t lhs_max, 
      intmax_t rhs_min, intmax_t rhs_max> 
    struct common_type<my_type<lhs_min, lhs_max>, 
        my_type<rhs_min, rhs_max>> { 
    using type = /* ... */; 
    }; 

} // std 

Es gibt Spezialisierungen bereits für std::chrono::duration und std::chrono::time_point, die nur ohne cv spezialisieren - und Ref-Qualifikatoren.

jedoch diese Spezialisierungen nur verwendet werden, wenn CV- und ref--Qualifikation sind nicht angegeben. Es ist nicht offensichtlich, dass sie nicht da,

static_assert(
    std::is_same<std::common_type<const std::chrono::milliseconds, 
           std::chrono::microseconds &&>::type, 
       std::chrono::microseconds>::value, ""); 

funktioniert gut gerade verwendet wird.

Ich war verwirrt, wie die Spezialisierung genutzt wurde, bis ich, dass es realisiert war es nicht. Die generische Implementierung von std::common_type<> verwendet decltype() auf einer if-else-Ausdruck, wie folgt aus:

template <typename Lhs, typename Rhs> 
using common_type_impl_t = 
    decay_t<decltype(true ? std::declval<Lhs>() : std::declval<Rhs>())>; 

Hinweis: Es verwendet SFINAE dieses zu holen, wenn es erfolgreich ist sonst nicht definiert Blätter geben.

Jetzt können wir testen, ob dies tatsächlich funktioniert für std::chrono::duration indem sie sie testet,

static_assert(
    std::is_same<common_type_impl_t<const std::chrono::milliseconds, 
            std::chrono::microseconds &&>, 
       std::chrono::microseconds>::value, ""); 

, die vergeht. Jetzt werfen Sie volatile hinein und es wird brechen, gerade als @ Vincent darauf hinwies, was weiter beweist, dass die Spezialisierungen nicht verwendet werden.

So ist meine Schlussfolgerung, dass einer der beiden passieren wird:

  • Die Standardimplementierung so ändern, dass die Spezialisierungen gewöhnen wird, selbst wenn es CV- und ref--Qualifikation vorhanden ist, in dem Fall, dass Sie kann den CommonType<> Wrapper werfen, der sowieso trivial ist, und Sie haben nur eine Spezialisierung definiert.
  • Die Standardimplementierung ändert sich nicht, aber Sie haben nur eine Spezialisierung definiert und stattdessen CommonType<>.
+0

Ich würde vermuten, dass das Chrono-Ding wegen der impliziten Konvertierungen von 'durations' funktioniert, nicht wegen Spezialisierungen von' common_type'. Was natürlich für das OP nicht funktioniert (der Ergebnistyp kann ein dritter Typ sein). Das "flüchtige" Ding funktioniert nicht, weil die Umwandlung ein "const &", nicht ein "Const volatile &" nimmt. – dyp