Wie kann ich verhindern, dass Benutzer einen Funktionsvorlagenparameter angeben und dessen Ableitung erzwingen?

8

Angenommen, ich habe eine Vorlagenfunktion:

template <typename A, typename B>
A fancy_cast(B)
{
    return {};
}

Die beabsichtigte Verwendung ist so etwas wie fancy_cast<int>(1.f).

Nichts hindert den Benutzer jedoch daran, den zweiten Vorlagenparameter manuell anzugeben: Dies fancy_cast<int, int>(1.f)würde Probleme verursachen.

Wie kann ich verhindern, typename Bdass etwas angegeben wird, und erzwingen, dass es abgeleitet wird?

Ich habe mir Folgendes ausgedacht:

// Using this wrapper prevents the code from being
// ill-formed NDR due to [temp.res]/8.3
template <auto V> inline constexpr auto constant_value = V;

template <
    typename A,
    typename ...Dummy,
    typename B,
    typename = std::enable_if_t<constant_value<sizeof...(Dummy)> == 0>
>
A fancy_cast(B)
{
    return {};
}

Es scheint zu funktionieren, ist aber äußerst umständlich. Gibt es einen besseren Weg?

HolyBlackCat
quelle

Antworten:

4

Was ist mit fancy_casteiner variablen Vorlage?

template <typename A>
struct fancy_cast_t {
    template <typename B>
    A operator()(B x) const { return x; }
};

template <typename A>
constexpr fancy_cast_t<A> fancy_cast {};

fancy_cast<int>(1.5);  // works
fancy_cast<int, int>(1.5);  // doesn't work
fancy_cast<int>.operator()<int>(1.5);  // works, but no one would do this
Brian
quelle
3

Dies ist nicht die effizienteste Lösung, aber Sie können eine Klasse erstellen, die einen Vorlagenparameter für den Typ enthält, in den konvertiert werden soll, und dann eine Konstruktorvorlage haben, die einen beliebigen Typ annimmt. Wenn Sie dann ein operator Tfür den Typ hinzufügen, mit dem Sie die Klasse instanziieren, können Sie diesen korrekten Wert zurückgeben. Das würde so aussehen

template<typename T>
struct fancy_cast
{
    T ret;
    template<typename U>
    fancy_cast(U u) : ret(u) {} // or whatever you want to do to convert U to T
    operator T() && { return std::move(ret); }
};

int main()
{
    double a = 0;
    int b = fancy_cast<int>(a);
}

Dies funktioniert, da der Vorlagenparameter für den Konstruktor nicht angegeben werden kann, da Sie ihn nicht aufrufen können.

NathanOliver
quelle
2

Ich habe eine gut aussehende Lösung gefunden.

Wir können ein Nicht-Typ-Parameterpaket verwenden, dessen Typ der Benutzer nicht erstellen kann. 1 ZB ein Verweis auf eine versteckte Klasse:

namespace impl
{
    class require_deduction_helper
    {
      protected:
        constexpr require_deduction_helper() {}
    };
}

using require_deduction = impl::require_deduction_helper &;

template <typename A, require_deduction..., typename B>
A fancy_cast(B)
{
    return {};
}

1 Wir müssen eine Lücke für die Konstruktion eines lassen deduction_barrier, sonst wäre der Code ein schlecht geformter NDR . Deshalb ist der Konstruktor geschützt.

HolyBlackCat
quelle
1
@ M.Mac Meinst du für alles Mögliche Aund B? Ich verstehe nicht, warum es nicht so wäre.
HolyBlackCat
Ich denke nicht, dass Sie brauchen protected, require_deduction...muss nicht leer sein (im Gegensatz zu enable_if_t<sizeof...(Ts) == 0>). Die Vorlage ist ungültig, da Sie einige Werte nicht erstellen können. Ebenso struct S{}; using member_ptr_t = void (S::*)();gilt, auch wenn Skeine Mitglieder hat.
Jarod42
@ Jarod42 Hmm. Während ich es lese, heißt es in der Klausel, dass Sie in der Lage sein müssen, eine gültige Spezialisierung mit einem nicht leeren Paket zu erstellen, oder es ist ein schlecht geformter NDR. member_ptr_tkann mit initialisiert werden nullptr, aber wenn es nicht könnte, würde ich sagen, dass Sie kein Parameterpaket daraus machen könnten.
HolyBlackCat