c++ - Detectando constexpr con SFINAE
templates c++11 (2)
Estoy trabajando en la actualización de algunos códigos C ++ para aprovechar la nueva funcionalidad en C ++ 11. Tengo una clase de rasgo con algunas funciones que devuelven tipos fundamentales que la mayoría de las veces, pero no siempre, devuelve una expresión constante. Me gustaría hacer diferentes cosas dependiendo de si la función es constexpr
o no. Se me ocurrió el siguiente enfoque:
template<typename Trait>
struct test
{
template<int Value = Trait::f()>
static std::true_type do_call(int){ return std::true_type(); }
static std::false_type do_call(...){ return std::false_type(); }
static bool call(){ return do_call(0); }
};
struct trait
{
static int f(){ return 15; }
};
struct ctrait
{
static constexpr int f(){ return 20; }
};
int main()
{
std::cout << "regular: " << test<trait>::call() << std::endl;
std::cout << "constexpr: " << test<ctrait>::call() << std::endl;
}
El parámetro extra int
/ ...
está allí, de modo que si ambas funciones están disponibles después de SFINAE , la primera se elige por resolución de sobrecarga.
Compilar y ejecutar esto con Clang 3.2 muestra:
regular: 0
constexpr: 1
Así que esto parece funcionar, pero me gustaría saber si el código es legal C ++ 11. Especialmente porque entiendo que las reglas para SFINAE han cambiado.
NOTA: He abierto aquí una pregunta sobre si el código OP es realmente válido. Mi ejemplo reescrito a continuación funcionará en cualquier caso.
pero me gustaría saber si el código es legal C ++ 11
Lo es, aunque el argumento de plantilla predeterminado puede considerarse un poco inusual. Personalmente, me gusta mejor el siguiente estilo, que es similar a cómo (lee: I) escribe un rasgo para verificar la existencia de una función , simplemente usando un parámetro de plantilla sin tipo y dejando fuera el decltype
:
#include <type_traits>
namespace detail{
template<int> struct sfinae_true : std::true_type{};
template<class T>
sfinae_true<(T::f(), 0)> check(int);
template<class>
std::false_type check(...);
} // detail::
template<class T>
struct has_constexpr_f : decltype(detail::check<T>(0)){};
Tiempo de explicación ~
Su código original funciona † porque el punto de instanciación de un argumento de plantilla predeterminado es el punto de instanciación de su plantilla de función, es decir, en su caso, en main
, por lo que no puede ser sustituido antes que eso.
§14.6.4.1 [temp.point] p2
Si se llama una plantilla de función de una manera que utiliza la definición de un argumento predeterminado de esa plantilla de función [...], el punto de creación de instancias del argumento predeterminado es el punto de instanciación de la plantilla de función [ ...].
Después de eso, son solo las reglas de SFINAE.
† Al menos eso creo, no está del todo claro en el estándar.
Impulsado por @ marshall-clow, armé una versión algo más genérica de un rasgo de tipo para detectar constexpr
. Lo std::invoke_result
en std::invoke_result
, pero como constexpr
depende de las entradas, los argumentos de la plantilla son para los valores pasados, en lugar de los tipos.
Es algo limitado, ya que los argumentos de la plantilla solo pueden ser un conjunto limitado de tipos , y todos son const cuando llegan a la llamada al método. Puede probar fácilmente un método constexpr
wrapper si necesita otros tipos, o valores no const para un parámetro de referencia.
Entonces algo más de ejercicio y demostración que un código realmente útil.
Y el uso de la template<auto F, auto... Args>
convierte en C ++ 17-only, que necesita gcc 7 o clang 4. MSVC 14.10.25017 no puede compilarlo.
namespace constexpr_traits {
namespace detail {
// Call the provided method with the provided args.
// This gives us a non-type template parameter for void-returning F.
// This wouldn''t be needed if "auto = F(Args...)" was a valid template
// parameter for void-returning F.
template<auto F, auto... Args>
constexpr void* constexpr_caller() {
F(Args...);
return nullptr;
}
// Takes a parameter with elipsis conversion, so will never be selected
// when another viable overload is present
template<auto F, auto... Args>
constexpr bool is_constexpr(...) { return false; }
// Fails substitution if constexpr_caller<F, Args...>() can''t be
// called in constexpr context
template<auto F, auto... Args, auto = constexpr_caller<F, Args...>()>
constexpr bool is_constexpr(int) { return true; }
}
template<auto F, auto... Args>
struct invoke_constexpr : std::bool_constant<detail::is_constexpr<F, Args...>(0)> {};
}