¿Es posible constexpr en C++11?


¿Es posible producir un valor booleano en tiempo de compilación basado en si una expresión de C++11 es o no una expresión constante (es decir, constexpr) en C++11? Algunas preguntas sobre SO se relacionan con esto, pero no veo una respuesta directa en ninguna parte.

Author: user2023370, 2012-11-09

5 answers

A partir de 2017, is_constexpr no es posible en C++11. Eso suena como una cosa extraña de decir, así que permítanme explicar un poco de la historia.

Primero, agregamos esta característica para resolver un defecto: http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1129

Johannes Schaub - litb publicó una macro de detección constexpr que se basó en la disposición de que las expresiones constantes son implícitamente noexcept. Esto funcionó en C++11, pero nunca fue implementado por al menos algunos compiladores (para instance, clang). Luego, como parte de C++17, evaluamos La Eliminación de Especificaciones de Excepción Obsoletas de C++17. Como efecto secundario de esa redacción, eliminamos accidentalmente esa disposición. Cuando el Grupo de Trabajo Básico examinó la posibilidad de volver a incluir la disposición, se dieron cuenta de que había algunos problemas graves al hacerlo. Puede ver todos los detalles en el informe de error de LLVM . Así que en lugar de agregarlo de nuevo, decidimos considerarlo un defecto contra todas las versiones de standard and retroactively removed it.

El efecto de esto es que no hay, que yo sepa, ninguna manera de detectar si una expresión es utilizable como una expresión constante.

 11
Author: David Stone,
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-11-28 18:05:54

Una vez lo escribí (EDITAR: ver a continuación para las limitaciones y explicaciones). De https://stackoverflow.com/a/10287598/34509 :

template<typename T> 
constexpr typename remove_reference<T>::type makeprval(T && t) {
  return t;
}

#define isprvalconstexpr(e) noexcept(makeprval(e))

Sin embargo, hay muchos tipos de expresiones constantes. La respuesta anterior detecta expresiones constantes prvalue.


Explicación

La expresión noexcept(e) da false iff e contiene

  • una llamada potencialmente evaluada a una función que no tiene una especificación de excepción no lanzadora a menos que la call es una expresión constante,
  • una expresión potencialmente evaluada throw,
  • una forma potencialmente evaluable de dynamic_cast o typeid.

Tenga en cuenta que la plantilla de función makeprval no se declara noexcept, por lo que la llamada debe ser una expresión constante para que la primera viñeta no se aplique, y esto es lo que abusamos. Necesitamos que las otras viñetas no se apliquen también, pero afortunadamente, tanto throw como dynamic_cast o typeid no se permiten en expresiones constantes tampoco, así que esto está bien.

Limitaciones

Desafortunadamente hay una limitación sutil, que puede o no importar para usted. La noción de "potencialmente evaluado" es mucho más conservadora que los límites de lo que se aplican las expresiones constantes. Así que lo anterior noexcept puede dar falsos negativos. Reportará que algunas expresiones no son expresiones constantes prvalue, aunque lo sean. Ejemplo:

constexpr int a = (0 ? throw "fooled!" : 42);
constexpr bool atest = isprvalconstexpr((0 ? throw "fooled!" : 42));

En el anterior {[14] } es falso, a pesar de que la inicialización de a éxito. Esto se debe a que para ser una expresión constante, basta con que las subexpresiones "malvadas" no constantes "nunca sean evaluadas", aunque esas subexpresiones malvadas sean potencialmente evaluadas, formalmente.

 27
Author: Johannes Schaub - litb,
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:26:01

Sí, esto es posible. Una forma de hacerlo (que es válida incluso con los cambios recientes noexcept) es aprovechar las reglas de conversión de C++11:

Una reducción de la conversión es una conversión implícita [...] de un tipo entero o tipo de enumeración sin ámbito a un tipo entero que no puede representar todos los valores del tipo original, excepto donde la fuente es una expresión constante cuyo valor después de promociones integrales encajará en el tipo de objetivo.

(énfasis mío). La inicialización de listas generalmente no permite reducir las conversiones, y cuando se combina con SFINAE podemos construir gadgets para detectar si una expresión arbitraria es una expresión constante:

// p() here could be anything
template<int (*p)()> std::true_type is_constexpr_impl(decltype(int{(p(), 0U)}));
template<int (*p)()> std::false_type is_constexpr_impl(...);
template<int (*p)()> using is_constexpr = decltype(is_constexpr_impl<p>(0));

constexpr int f() { return 0; }
int g() { return 0; }
static_assert(is_constexpr<f>());
static_assert(!is_constexpr<g>());

Demostración en Vivo.

La clave aquí es que int{(expr, 0U)} contiene una conversión estrecha de unsigned int a int (y por lo tanto está mal formada), a menos que expr es una expresión constante, en cuyo caso la expresión completa (expr, 0U) es una expresión constante cuyo valor evaluado encaja en el tipo int.

 3
Author: Richard Smith,
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
2018-05-04 07:05:13

Lo siguiente es una implementación de is_constexpr para funciones, no para expresiones arbitrarias, para C++11 y C++17. Sin embargo, requiere que los argumentos de la función que desea probar sean construibles por defecto.

#include <type_traits>

struct A {};  // don't make it too easy, use a UDT

          A f1(A a) { return a; }  // is_constexpr -> false
constexpr A f2(A a) { return a; }  // is_constexpr -> true

// The following turns anything (in our case a value of A) into an int.
// This is necessary because non-type template arguments must be integral 
// (likely to change with C++20).
template <class T> constexpr int make_int(T &&) { return 0; }

// Helper to turn some function type (e.g. int(float)) into a function
// pointer type (e.g. int (*)(float)).
template <class T> struct signature_from;
template <class R, class... Args> struct signature_from<R(Args...)> {
    using type = R(*)(Args...);
};

// See std::void_t for the idea. This does it for ints instead of types.
template <int...> using void_from_int = void;

// The fallback case: F is not a function pointer to a constexpr function
template <class T, typename signature_from<T>::type F, class = void_from_int<>>
struct is_constexpr {
    static constexpr bool value = false;
};
// If void_from_int<make_int(F(Args()...))> doesn't lead to a substitution
// failure, then this is the preferred specialization. In that case F must
// be a function pointer to a constexpr function. If it is not, it could
// not be used in a template argument.
template <class R, class... Args, typename signature_from<R(Args...)>::type F>
struct is_constexpr<R(Args...), F, void_from_int<make_int(F(Args()...))>>
{
    static constexpr bool value = true;
};

// proof that it works:
static_assert(!is_constexpr<A(A), f1>::value, "");
static_assert( is_constexpr<A(A), f2>::value, "");

#if __cplusplus >= 201703
// with C++17 the type of the function can be deduced:
template<auto F> struct is_constexpr2 : is_constexpr<std::remove_pointer_t<decltype(F)>, F> {};

static_assert(!is_constexpr2<f1>::value, "");
static_assert( is_constexpr2<f2>::value, "");
#endif

Véalo en acción en https://godbolt.org/g/rdeQme .

 1
Author: Vir,
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
2018-04-20 14:04:48

Hagamos un juego ingenuo con el modismo SFINAE:

template <typename C> struct IsConstExpr
{
    typedef char yes;
    typedef char no[2];

    template <typename T> static constexpr yes& swallow(T) { int x[T()]; return 0; };
    template <typename T> static no& swallow(...);

    static const int value = sizeof(swallow<C>(0)) == sizeof(yes);
};

El código anterior es sintácticamente incorrecto, pero nos dará una idea. Vamos a tratar de hacer uso de ella:

constexpr int f() { return 32167; }

int g() { return 32167; }

int main()
{
   std::cout << IsConstExpr<decltype(&f)>::value << std::endl;
}

El compilador dice:

In instantiation of 'static constexpr IsConstExpr<C>::yes& IsConstExpr<C>::swallow(T) [with T = int (*)(); C = int (*)(); IsConstExpr<C>:
:yes = char]':

Ahora el problema es obvio: el parámetro de la plantilla T = int (*)();

Significa que constexpr no es parte del tipo y no podemos detectar.

 -3
Author: Sergey K.,
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
2012-11-09 08:30:16