Конструктор constexpr не вызывается как constexpr для неявного преобразования типа

Я сделал код, который может быть отправлен в функцию на основе сайта вызова, предоставляющего строку, связанную с данной функцией (через tupleуказателей на функции и параллельного массива). Вместо того, чтобы принимать строку напрямую, функция диспетчеризации принимаетCallable типа, где const char* конвертируется в Callable.

Конструктор Callable является constexpr, и ищет функцию из отмеченного tupleс базовым рекурсивным поиском. Я убедился, что конструктор может работать правильно и создаватьconstexpr Callable(пример включен). Поскольку диспетчерская функция получает аргументы для передачи вCallableс operator(), Я знаю ожидаемую сигнатуру функции Callableс operator() в то время, когда я его создаю.

Я пытаюсь выполнить две проверки во время компиляции, когда они могут быть выполнены во время компиляции. Сначала я проверяю, существует ли предоставленная строка в заранее определенном массиве строк. Во-вторых, я проверяю, что подпись функции, связанной с этой строкой, соответствует ожидаемой подписи изtupleуказателей на функции. Я создаю "дружественные" сообщения об ошибках во время компиляцииthrow()внутри constexpr метод, который ищет функцию.

Я проверил это, создав constexprcallable, я получаю ожидаемые сообщения об ошибках во время компиляции. Это работает. Что не работает, так это получение сообщений времени компиляции, если я использую свойDispatcher напрямую, позволяя сайту вызова преобразовать строку в Callable. Я знаю, что когда я использую параметры времени выполнения, моя функция диспетчеризации не будет вызываться вconstexpr контекст - я намеренно не выполнял эту функцию constexpr; дело в том, чтобы вызвать его со значениями времени выполнения. Но я думал, что неявные преобразования "происходят на месте вызова", а не внутри вызываемой функции.

Поэтому подумал, что в звонке вроде dispatcher("one", 1) (который вызывает первую функцию с параметром 1) будет выглядеть так: "one" преобразуется в Callable на сайте вызова, тогда вызов будет выполнен какdispatcher(Callable("one"), 1). Это означало бы, чтоconstexprКонструктор может быть использован, по крайней мере. По моему опыту, если вы не игнорируете результатconstexpr звонок, звонок осуществляется как constexprесли это возможно, иначе это делается как среда выполнения. См. Функции Constexpr, которые не вызываются во время компиляции, если результат игнорируется. Этого не происходит - конструктор преобразования вызывается во время выполнения, когда преобразование происходит во время вызова моей функции диспетчеризации!

Кто-нибудь знает, как я могу изменить свой код, чтобы конструктор преобразования вызывал во время компиляции, если это возможно??? В этом посте я нашел совершенно другое решение для решения этой общей проблемы, но, честно говоря, мне больше нравится синтаксис приведенного ниже кода, если бы я мог заставить его работать.

Я не собираюсь включать приведенный выше код в тело этого сообщения, а скорее буду включать более канонический пример, демонстрирующий поведение, а также поведение, которое я видел в сообщении, на которое я ссылался выше, все в одном.

Живая демонстрация нижеприведенного: https://onlinegdb.com/r1s1OE77v

Живая демонстрация моей "реальной" проблемы, если интересно: https://onlinegdb.com/rJCQ2bGXw

Сначала "тестовые приборы":

// Modified from https://stackru.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
}

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

Демо.

Но не совсем лучше, чем предлагалось dispatcher.dispatch(MAKE_CHAR_SEQ("a"), 1); (или с расширением dispatcher.dispatch("a"_cs, 1);) (обеспечивая перегрузку диспетчеризации, чтобы иметь возможность создавать constexpr Callable).

Другие вопросы по тегам