Tenho dois tipos, CustomNotCompatibleWithRun
, CustomCompatibleWithRun
, e uma run()
função:
#include <iostream>
#include <type_traits>
template <typename T_>
struct CustomNotCompatibleWithRun { using T = T_; };
template <typename T_>
struct CustomCompatibleWithRun {
using T = T_;
CustomCompatibleWithRun operator+(const CustomCompatibleWithRun& other)
{
CustomCompatibleWithRun res;
res.a = a + other.a;
res.b = b + other.b;
return res;
}
void print() const
{
std::cout << "a, b = " << a << ", " << b << "\n";
}
T a;
T b;
};
template <typename T>
T run(T a, T b) { return a+b; }
Quero escrever uma característica de tipo, IsTypeCompatibleWithRun
, que verifica se um dado Type
(cujo único contrato é ter um alias de tipo Type::T
) pode ser usado na run()
função. Ou seja:
int main() {
using T = float;
CustomCompatibleWithRun<T> obj1{.a = 1.f, .b = 2.f};
CustomCompatibleWithRun<T> obj2{.a = 2.f, .b = 3.f};
obj1.print(); // a, b = 1, 2
obj2.print(); // a, b = 2, 3
const auto obj3 = obj1 + obj2;
obj3.print(); // a, b = 3, 5
const auto obj4 = run(obj1, obj2);
obj4.print(); // a, b = 3, 5
CustomNotCompatibleWithRun<T> obj5{};
CustomNotCompatibleWithRun<T> obj6{};
// const auto obj7 = obj5 + obj6; // does not compile
std::cout << "IsTypeCompatibleWithRun<CustomCompatibleWithRun>::value: " << IsTypeCompatibleWithRun<CustomCompatibleWithRun<T>>::value << "\n"; // should print 1
std::cout << "IsTypeCompatibleWithRun<CustomNotCompatibleWithRun>::value: " << IsTypeCompatibleWithRun<CustomNotCompatibleWithRun<T>>::value << "\n"; // should print 0
}
Tentei as três opções abaixo:
template <typename Type, typename = void>
struct IsTypeCompatibleWithRun : std::false_type {};
// Option 1
template <typename Type>
struct IsTypeCompatibleWithRun<Type, decltype(void(run<typename Type::T>(std::declval<typename Type::T>(), std::declval<typename Type::T>())))> : std::true_type {};
// Option 2
template <typename Type>
struct IsTypeCompatibleWithRun<Type, decltype(void(std::declval<Type>() + std::declval<Type>()))> : std::true_type {};
// Option 3
template <typename Type>
struct IsTypeCompatibleWithRun<Type, std::void_t<decltype(run(std::declval<Type>(), std::declval<Type>()))>> : std::true_type {};
E apenas a Opção 2 funciona — pelo que entendi, isso ocorre porque forçamos o +
operador dentro da própria característica de tipo, enquanto nas Opções 1 e 3, apenas a assinatura da run()
função é verificada. No entanto, estou insatisfeito com a Opção 2 porque ela exige a repetição do corpo da função run()
na própria característica de tipo. Neste exemplo simples, funciona bem, mas na minha aplicação, o corpo da run()
função é muito mais complexo e é impossível repeti-lo dentro da característica de tipo.
Então tentei mudar a assinatura de run()
para
template <typename T>
auto run(T a, T b)
mas o código nem compilava mais. Então tentei adicionar um tipo de retorno final
template <typename T>
auto run(T a, T b) -> decltype(a+b)
e agora a Opção 3 funcionaria, mas não a Opção 1. Por que isso?
De qualquer forma, o acima envolveria ter que repetir a a+b
parte no tipo de retorno final, então não é uma solução para mim.
Existe uma maneira de obter esse traço de tipo sem alterar run()
e depender apenas run()
(e não repetir) seu corpo de função em C++ 14?