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?

HolyBlackCat
fonte

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
Brian
fonte
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.

NathanOliver
fonte
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.

HolyBlackCat
fonte
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