constructor constexpr no llamado como constexpr para conversión de tipo implícito
Hice un código que es capaz de enviar a una función basada en el sitio de llamada que proporciona una cadena asociada con una función dada (a través tuplede punteros de función y una matriz paralela). En lugar de aceptar una cadena directamente, la función de envío acepta un Callabletipo, donde a const char*se puede convertir en Callable.
El constructor de Callablees constexpry busca una función de la anotada tuplecon una búsqueda recursiva básica. He verificado que el constructor es capaz de funcionar correctamente y crear un constexpr Callable(ejemplo incluido). Dado que la función de envío recibe los argumentos para pasar a los Callable, operator()conozco la firma de función esperada de los Callable's operator()en el momento en que la creo.
Estoy tratando de realizar dos comprobaciones en tiempo de compilación, cuando se pueden hacer en tiempo de compilación. Primero, verifico que la cadena proporcionada exista en la matriz predefinida de cadenas, en absoluto. En segundo lugar, verifico que la firma de la función asociada con esa cadena coincida con la firma esperada de los tuplepunteros de función. Creo mensajes de error "amistosos" en tiempo de compilación haciendo throw()'ing dentro del constexprmétodo que busca la función.
Verifiqué que al crear un constexprinvocable, obtengo los mensajes de error esperados en tiempo de compilación. Esto funciona. Lo que no funciona es recibir mensajes en tiempo de compilación si uso my Dispatcherdirectamente, lo que permite que el sitio de llamadas convierta una cadena en un archivo Callable. Sé que cuando uso parámetros de tiempo de ejecución, mi función de envío no se llamará en un constexprcontexto; intencionalmente no hice esa función constexpr; el punto es llamarlo con valores de tiempo de ejecución. Pero pensé que las conversiones implícitas "suceden en el sitio de la llamada" , no dentro de la función llamada.
Por lo tanto, pensé que en una llamada similar dispatcher("one", 1)(que llama a la primera función con un parámetro de 1) se vería así: "uno" se convierte en a Callable en el sitio de llamada , luego se realiza una llamada como dispatcher(Callable("one"), 1). Eso significaría que el constexprconstructor podría usarse, al menos. En mi experiencia, siempre que no ignore el resultado de una constexprllamada, la llamada se realiza como constexprsi pudiera ser, de lo contrario, se realiza como tiempo de ejecución. Consulte las funciones de Constexpr que no se llaman en tiempo de compilación si se ignora el resultado . Esto no está sucediendo: ¡ el constructor de conversión se llama en tiempo de ejecución cuando la conversión ocurre dentro de una llamada a mi función de envío!
¿Alguien sabe de alguna manera en que pueda cambiar mi código para que se llame al constructor de conversión en tiempo de compilación, si es posible? Encontré una solución totalmente diferente para resolver esta clase general de problema en esta publicación , pero, francamente, me gusta más la sintaxis del código a continuación, si pudiera hacerlo funcionar.
No voy a incluir el código anterior en el cuerpo de esta publicación, sino que incluiré un ejemplo más canónico que demuestre el comportamiento y también muestre el comportamiento que vi en la publicación a la que me referí anteriormente, todo en uno.
Demostración en vivo de lo siguiente:https://onlinegdb.com/r1s1OE77v
Demostración en vivo de mi problema "real", si está interesado:https://onlinegdb.com/rJCQ2bGXw
Primero los "dispositivos de prueba":
// Modified from https://stackoverflow.com/a/40410624/12854372
// In a constexpr context, ContextIsConstexpr1(size_t) always
// simply sets _s to 1 successfully.
extern bool no_symbol_s_is_zero;
struct ContextIsConstexpr1 {
size_t _s;
constexpr ContextIsConstexpr1(size_t s) : _s(s ? 1 : no_symbol_s_is_zero) {}
};
// In a constexpr context, ContextIsConstexpr2(size_t) will cause
// a compile-time error if 0 is passed to the constructor
struct ContextIsConstexpr2 {
size_t _s;
constexpr ContextIsConstexpr2(size_t s) : _s(1) {
if(!s) {
throw logic_error("s is zero");
}
}
};
// Accept one of the above. By using a CONVERSION constructor
// and passing in a size_t parameter, it DOES make a difference.
ContextIsConstexpr1 foo(ContextIsConstexpr1 c) { return c; }
ContextIsConstexpr2 bar(ContextIsConstexpr2 c) { return c; }
Ahora el código de prueba:
int main()
{
constexpr size_t CONST = 1;
#define TEST_OBVIOUS_ONES false
// ------------------------------------------------------------
// Test 1: result is compile-time, param is compile-time
// ------------------------------------------------------------
#if TEST_OBVIOUS_ONES
// Compile-time link error iif s==0 w/ any optimization (duh)
constexpr auto test1_1 = ContextIsConstexpr1(CONST);
cout << test1_1._s << endl;
// Compile-time throw iif s==0 w/ any optimization (duh)
constexpr auto test1_2 = ContextIsConstexpr2(CONST);
cout << test1_2._s << endl;
#endif
// ------------------------------------------------------------
// Test 2: result is runtime, param is compile-time
// ------------------------------------------------------------
// Compile-time link error iif s==0 w/ any optimization ***See below***
auto test2_1 = ContextIsConstexpr1(CONST);
cout << test2_1._s << endl;
// Runtime throw iif s==0 w/ any optimization
// NOTE: Throw behavior is different than extern symbol behavior!!
auto test2_2 = ContextIsConstexpr2(CONST);
cout << test2_2._s << endl;
// ------------------------------------------------------------
// Test 3: Implicit conversion
// ------------------------------------------------------------
// Compile-time link error if (1) s==0 w/ any optimization *OR* (2) s>0 w/ low optimization!!
// Note: New s>0 error due to implicit conversion ***See above***
auto test3_1 = foo(CONST);
cout << test3_1._s << endl;
// Runtime throw iif s==0 w/ any optimization
auto test3_2 = bar(CONST);
cout << test3_2._s << endl;
// ------------------------------------------------------------
// Test 4: result is ignored, param is compile-time
// ------------------------------------------------------------
// Compile-time link error w/ any 's' iif low optimization
// Note: no error w/ s==0 with high optimization, new error w/ s>0 by ignoring result ***See above***
ContextIsConstexpr1{CONST};
// Runtime throw iif s==0 w/ any optimization
ContextIsConstexpr2{CONST};
// ------------------------------------------------------------
// Get runtime input, can't optimize this for-sure
// ------------------------------------------------------------
#if TEST_OBVIOUS_ONES
size_t runtime;
cout << "Enter a value: ";
cin >> runtime;
// ------------------------------------------------------------
// Test 5: result is runtime, param is runtime
// ------------------------------------------------------------
// Compile-time link error w/ any 's' w/ any optimization (duh)
auto test5_1 = ContextIsConstexpr1(runtime);
cout << test5_1._s << endl;
// Runtime throw iif s==0 w/ any optimization (duh)
auto test5_2 = ContextIsConstexpr2(runtime);
cout << test5_2._s << endl;
// ------------------------------------------------------------
// Test 6: result is ignored, param is runtime
// ------------------------------------------------------------
// Compile-time link error w/ any 's' w/ any optimization (duh)
ContextIsConstexpr1{runtime};
// Runtime throw iif s==0 w/ any 's' w/ any optimization (duh)
ContextIsConstexpr2{runtime};
#endif
}
Respuestas
¿Alguien sabe de alguna manera en que pueda cambiar mi código para que se llame al constructor de conversión en tiempo de compilación, si es posible?
Como dije en la publicación vinculada, la llamada de constexprfunciones en tiempo de compilación se realiza solo en expresión constante.
Los parámetros no son constexpr.
Una solución sería usar MACRO:
#define APPLY_DISPATCHER(dispatcher, str, ...) \
do { \
constexpr callable_type_t<decltype(dispatcher), decltype(make_tuple(__VA_ARGS__))> callable(str); \
(dispatcher)(callable, __VA_ARGS__); \
} while (0)
con
template <typename Dispatcher, typename Tuple> struct callable_type;
template <typename Dispatcher, typename ... Ts>
struct callable_type<Dispatcher, std::tuple<Ts...>>
{
using type = typename Dispatcher::template Callable<Ts...>;
};
template <typename Dispatcher, typename Tuple>
using callable_type_t = typename callable_type<Dispatcher, Tuple>::type;
Con uso:
APPLY_DISPATCHER(dispatcher, "one", 1);
APPLY_DISPATCHER(dispatcher, "a", 1); // Fail at compile time as expected
demostración _
Pero en realidad no es mejor que el propuesto dispatcher.dispatch(MAKE_CHAR_SEQ("a"), 1);(o con la extensión dispatcher.dispatch("a"_cs, 1);) (lo que proporciona una sobrecarga de despacho para poder crear constexpr Callable).