Como impedir que o usuário especifique um parâmetro de modelo de função, forçando sua dedução?


8

Digamos que eu tenho uma função de modelo:

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

O uso pretendido é algo parecido fancy_cast<int>(1.f).

Mas nada impede que o usuário especifique o segundo parâmetro do modelo manualmente:, o fancy_cast<int, int>(1.f)que causaria problemas.

Como impedir que typename Bseja especificado e forçar a dedução?

Eu vim com isso:

// 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, mas é extremamente complicado. Existe uma maneira melhor?

Respostas:


4

Que tal criar fancy_castum modelo de variável?

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

3

Essa não é a solução mais eficiente, mas você pode criar uma classe que tenha um parâmetro de modelo para o qual converter o tipo e, em seguida, ter um modelo de construtor que aceite qualquer tipo. Então, se você adicionar um operator Tpara o tipo que instancia a classe, poderá obter esse valor correto. Isso pareceria

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

Isso funciona porque não há como especificar o parâmetro de modelo para o construtor, pois você não pode chamá-lo.


2

Encontrei uma solução bonita.

Podemos usar um pacote de parâmetros não-tipo, de um tipo que o usuário não pode construir. 1 Por exemplo, uma referência a uma classe 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 Temos que deixar uma brecha para a construção de a deduction_barrier; caso contrário, o código seria NDR mal formado . É por isso que o construtor está protegido.


1
@ M.Mac Você quer dizer para todos os possíveis Ae B? Não vejo por que não.
91119 HolyBlackCat

Eu não acho que você precisa protected, require_deduction...não precisa estar vazio (ao contrário de enable_if_t<sizeof...(Ts) == 0>). Não é porque você não pode construir alguns valores que o modelo é inválido. Da mesma forma, struct S{}; using member_ptr_t = void (S::*)();é válido, mesmo que Snão tenha membros.
Jarod42

@ Jarod42 Hmm. Enquanto eu lia, a cláusula diz que você deve ser capaz de criar uma especialização válida com um pacote não vazio ou com uma notificação de falha na entrega mal formada. member_ptr_tpode ser inicializado com nullptr, mas se não pudesse, diria que você não pode criar um pacote de parâmetros com ele.
9139 HolyBlackCat
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.