constexpr 构造函数未调用为隐式类型转换的 constexpr

constexpr constructor not called as constexpr for implicit type conversion

我编写了一些代码,这些代码能够根据提供与给定函数关联的字符串的调用站点分派到函数(通过 tuple 函数指针和并行数组)。 dispatch 函数不是直接接受字符串,而是接受 Callable 类型,其中 const char* 可转换为 Callable.

Callable 的构造函数是 constexpr,并通过基本的递归搜索从注意到的 tuple 中查找一个函数。我已经验证构造函数能够正常工作并创建 constexpr Callable(包括示例)。由于调度函数接收要传递给 Callableoperator() 的参数,我在创建它时知道 Callableoperator() 的预期函数签名.

我正在尝试在编译时执行两项检查,当它们可以在编译时完成时。首先,我检查提供的字符串是否完全存在于预定义的字符串数组中。其次,我检查与该字符串关联的函数的签名是否与函数指针 tuple 中的预期签名匹配。我在编译时通过 throw() 在查找函数的 constexpr 方法中创建“友好”错误消息。

我已经通过创建 constexpr 可调用文件验证了我在编译时收到了预期的错误消息。这行得通。如果我直接使用我的 Dispatcher,让调用站点将字符串转换为 Callable,则无法获取编译时消息。我知道当我使用运行时参数时,我的调度函数不会在 constexpr 上下文中被调用——我故意没有创建该函数 constexpr;关键是用运行时值调用它。但是 我认为隐式转换“发生在调用点”,而不是在被调用函数中。

因此,我认为在像 dispatcher("one", 1) 这样的调用中(它调用参数为 1 的第一个函数)看起来像:“one”被转换为 Callable 在调用站点,然后调用dispatcher(Callable("one"), 1)。这意味着至少可以使用 constexpr 构造函数 。以我的经验,只要你不忽略 constexpr 调用的结果,如果可以的话,调用将作为 constexpr 进行,否则它被制作为运行时。参见 。这没有发生——转换构造函数在运行时被调用当转换发生在对我的调度函数的调用中时!

有谁知道我可以更改我的代码以使转换构造函数在编译时被调用的方法吗???我在 中找到了一个完全不同的解决方案来解决这个一般的 class 问题,但坦率地说,如果我能让它工作的话,我更喜欢下面代码的语法。

我不会将上面的代码包含在此 post 的正文中,而是包含一个更规范的示例来演示行为并显示我在 [=78= 中看到的行为] 我在上面引用过,一体机。

下面的现场演示:https://onlinegdb.com/r1s1OE77v

我的“真实”问题的现场演示,如果有兴趣:https://onlinegdb.com/rJCQ2bGXw

首先是“测试装置”:

// Modified from 

// 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; }

现在测试代码:

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
}

Does anyone know of a way I can change my code to get the conversion constructor to be called at compile-time if it can be

正如我在链接帖子中所说,编译时 constexpr 函数的调用在常量表达式中完成

参数不是constexpr。

一种解决方法是使用 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)

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;

使用情况:

APPLY_DISPATCHER(dispatcher, "one", 1);
APPLY_DISPATCHER(dispatcher, "a", 1); // Fail at compile time as expected

Demo.

但并不比建议的dispatcher.dispatch(MAKE_CHAR_SEQ("a"), 1);(或扩展dispatcher.dispatch("a"_cs, 1);)好(提供调度重载以能够创建constexpr Callable)。