¿Cómo puedo evitar que el usuario especifique un parámetro de plantilla de función, forzando que se deduzca?

8

Digamos que tengo una función de plantilla:

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

El uso previsto es algo así fancy_cast<int>(1.f).

Pero nada impide que el usuario especifique manualmente el segundo parámetro de plantilla: fancy_cast<int, int>(1.f)lo que causaría problemas.

¿Cómo puedo evitar que typename Bse especifique y forzar que se deduzca?

Se me ocurrió esto:

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

Parece funcionar, pero es extremadamente engorroso. ¿Hay una mejor manera?

HolyBlackCat
fuente

Respuestas:

4

¿Qué hay de hacer fancy_castuna plantilla variable?

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
fuente
3

Esta no es la solución más eficiente, pero puede crear una clase que tenga un parámetro de plantilla para el tipo a convertir y luego tener una plantilla de constructor que tome cualquier tipo. Luego, si agrega un operator Tpara el tipo con el que crea una instancia de la clase, puede obtener ese valor correcto. Eso se vería como

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

Esto funciona porque no hay forma de especificar el parámetro de plantilla para el constructor, ya que en realidad no puede llamarlo.

NathanOliver
fuente
2

Encontré una solución atractiva.

Podemos usar un paquete de parámetros no tipo, de un tipo que el usuario no puede construir. 1 Por ejemplo, una referencia a una clase oculta:

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 Tenemos que dejar un vacío para construir un deduction_barrier, de lo contrario, el código sería un NDR mal formado . Por eso el constructor está protegido.

HolyBlackCat
fuente
1
@ M.Mac ¿Te refieres a todo lo posible Ay B? No veo por qué no lo haría.
HolyBlackCat
No creo que lo necesite protected, require_deduction...no requiere estar vacío (al contrario de enable_if_t<sizeof...(Ts) == 0>). No es porque no pueda construir algunos valores que la plantilla no sea válida. Del mismo modo, struct S{}; using member_ptr_t = void (S::*)();es válido, incluso si Sno tiene miembros.
Jarod42
@ Jarod42 Hmm. Mientras lo leo, la cláusula dice que tienes que poder crear una especialización válida con un paquete no vacío, o es un NDR mal formado. member_ptr_tse puede inicializar con nullptr, pero si no pudiera, diría que no se puede hacer un paquete de parámetros.
HolyBlackCat