Tenho um monte de sobrecargas de funções, consistindo principalmente em duas assinaturas:
void func(const my_type1&);
void func(const my_type2&, hash_t);
e uma função que tenta chamar func com base na existência da função:
template <typename T>
void func_imp(const T& t, hash_t hash){
if constexpr (has_func_with_2nd_param_v<T>){
func(t, hash);
}else{
func(t);
}
}
mas estou tendo problemas de conversão que acabam chamando a sobrecarga 'errada':
#include <type_traits>
#include <cstdio>
struct hash_t{
};
struct my_type1{
my_type1() {}
};
struct my_type2{
my_type2() {}
my_type2(const my_type1&){}
};
void func(const my_type1&){
printf("no hash\n");
}
void func(const my_type2&, hash_t=hash_t()){
printf("hash\n");
}
template<typename T, typename = void>
struct has_func_with_2nd_param : std::false_type {};
template<typename T>
struct has_func_with_2nd_param<T, std::void_t<decltype(func(std::declval<T>(), std::declval<hash_t>()))>> : std::true_type {};
template<typename T>
constexpr bool has_func_with_2nd_param_v = has_func_with_2nd_param<T>::value;
// I want to route to the appropriate function without conversion routing to the wrong func
template <typename T>
void func_imp(const T& t, hash_t hash){
if constexpr (has_func_with_2nd_param_v<T>){
func(t, hash);
}else{
func(t);
}
}
int main()
{
// this is true as expected
static_assert(has_func_with_2nd_param_v<my_type2>);
// this is also true, since my_type2 is constructable from my_type1 and then finds finds func(my_type2, hash_t)
static_assert(has_func_with_2nd_param_v<my_type1>);
func_imp(my_type1(), hash_t()); // prints "hash", but wanting it to call the "no hash" version
func_imp(my_type2(), hash_t()); // prints "hash"
return 0;
}
Estou com dificuldades para encontrar a metafunção correta para obter o comportamento desejado. Alguém tem alguma ideia?
godbolt aqui
Você pode usar a regra que permite, no máximo, uma conversão de tipo definida pelo usuário e adicionar um tipo fictício que imponha outra conversão de tipo definida pelo usuário. Dessa forma, você precisará ter uma correspondência exata de tipo para os tipos originais.
A struct
ForceExactClassType
é usada apenas para impor uma conversão definida pelo usuário em contextos não avaliados, portanto, uma definição paraoperator const T&()
não é necessária. Você pode precisar ajustar as partesconst
e&
dependendo das suas necessidades, mas o mecanismo subjacente é o mesmo.Para impor a conversão extra, a característica do tipo deve ser alterada para
Então, pelo menos para o exemplo que você mostrou, ele se comporta como esperado.
Saídas:
Até certo ponto, a
is_exactly_invocable
característica que você mencionou em um comentário a uma resposta agora excluída pode ser implementada comoObserve que isso tem a limitação de funcionar apenas para tipos de classe. Não funcionaria para tipos que podem ser convertidos implicitamente sem passar por uma função de conversão definida pelo usuário. Também não funcionaria se
Fn
fosse um functor que tivesse um modelo,operator()
pois conversões não são consideradas nas deduções de parâmetros de modelo (apontado em um comentário de @ Jarod42 ).Com a característica geral definida acima, as seguintes afirmações são válidas:
Demonstração: https://godbolt.org/z/Kqr1n14b6