Detección de constexpr con SFINAE


Estoy trabajando en la actualización de código C++ para aprovechar la nueva funcionalidad en C++11. Tengo una clase trait con algunas funciones que devuelven tipos fundamentales que la mayoría de las veces, pero no siempre, devolverían una expresión constante. Me gustaría hacer cosas diferentes basadas en 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 extra int/... parámetro está allí de modo que si ambas funciones están disponibles después de SFINAE , el el primero se elige sobrecargando la resolución.

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.

Author: Xeo, 2013-03-05

2 answers

NOTA: Abrí una pregunta aquí acerca de si el código OPs 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 por defecto de la plantilla puede ser considerado un poco inusual. Personalmente, me gusta más el siguiente estilo, que es similar a cómo (léase: I) escribe un rasgo para comprobar la existencia de una función , simplemente usando un parámetro de plantilla no de tipo y omitiendo 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)){};

Ejemplo en vivo.


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, lo que significa, en su caso, en main, por lo que no se puede sustituir antes que eso.

§14.6.4.1 [temp.point] p2

Si una plantilla de función [...] se llama de una manera que utiliza la definición de un argumento predeterminado de esa plantilla de función [...], el punto de instanciación del argumento por defecto es el punto de instanciación de la plantilla de función [...].

Después de eso, solo son las reglas habituales de SFINAE.


† Al menos creo que sí, no es completamente claro en el estándar.

 13
Author: Xeo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:34:23

Solicitado por @marshall-clow, armé una versión algo más genérica de un rasgo de tipo para detectar constexpr. Lo modelé en std::invoke_result, pero debido a que 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 args de 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 de envoltura constexpr si lo necesita otros tipos, o lvalues no constantes para un parámetro de referencia.

Así que algo más de un ejercicio y una demostración que un código realmente útil.

Y el uso de template<auto F, auto... Args> hace que sea solo C++17, necesitando 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)> {};

}

Demostración en Vivo con casos de uso en wandbox

 1
Author: TBBle,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:10:03