【问题标题】:constexpr constructor not called as constexpr for implicit type conversionconstexpr 构造函数不被称为隐式类型转换的 constexpr
【发布时间】:2020-12-13 17:38:28
【问题描述】:

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

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

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

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

因此,我认为在像 dispatcher("one", 1) 这样的调用中(调用第一个参数为 1 的函数)看起来像:“one”被转换为 Callable 在调用站点,然后调用dispatcher(Callable("one"), 1)。这意味着至少可以使用constexpr 构造函数可以。以我的经验,只要您不忽略constexpr 调用的结果,调用将作为constexpr 进行(如果可以),否则将作为运行时进行。见Constexpr functions not called at compile-time if result is ignored。这不会发生 - 在运行时调用转换构造函数,当转换发生在对我的调度函数的调用中时!

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

我不打算在这篇文章的正文中包含上述代码,而是包含一个更规范的示例来演示该行为并显示我在上面引用的帖子中看到的行为,一体化.

以下内容的现场演示:https://onlinegdb.com/r1s1OE77v

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

首先是“测试夹具”:

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

现在是测试代码:

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
}

【问题讨论】:

    标签: c++ string runtime constexpr constexpr-function


    【解决方案1】:

    有谁知道我可以更改代码以在编译时调用转换构造函数的方法

    正如我在链接帖子中所说,在编译时调用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);)更好(提供调度重载以能够创建constexprCallable)。

    【讨论】:

    • 再次感谢@Jarod42。但在我将此标记为已回答之前,您能否将您的陈述“......仅在常量表达式中完成”与 stackoverflow.com/q/63422516/12854372 的结果相协调? (或者您可以跳过帖子并查看onlinegdb.com/rJao0RNGP 的现场演示)似乎如果constexpr 调用的结果甚至保存在运行时变量中,那么在该代码中,该函数被称为constexpr。奇怪的是结果似乎不同 - 我想知道这是否是未定义行为的土地???
    • 程序格式不正确(NDR),只是优化器删除了包括no_symbol在内的一些代码,即使没有constexprDemo(添加-O2)所以我们看不到问题。
    • 您先生,是一位绅士和一位学者。但是,唉,我从您的演示链接中得到了相同的结果——auto result2 是一个constexpr 调用,即使使用-O2 也是如此。我尝试在onelinegdbwandbox 中设置-O2 自己:wandbox.org/permlink/0rtieqf1SxSykQI7 将在一分钟内尝试godbolt
    • 更改了我的帖子正文以显示上述更“规范”的示例。我认为通过将旧帖子的结果与使用隐式转换的结果并排显示更容易看出其中的奥秘。在“旧帖”结果中,行为与优化级别无关。使用隐式转换,它不是。
    • 这不是 constexpr,而是优化的,与 why-does-c-stdmin-cant-use-a-static-field-as-its-parameter-when-compile-on 类似的问题:您的诊断被优化器“隐藏”(但由于它是格式错误的 NDR,错误消息无论如何都是可选的...... )。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-10-31
    • 1970-01-01
    • 2020-12-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-09-23
    相关资源
    最近更新 更多