Приведение в исполнение функции контракта во время компиляции, когда это возможно
(этот вопрос был вдохновлен тем, как я могу сгенерировать ошибку компиляции, чтобы предотвратить использование определенного VALUE (не типа) в функции?)
Допустим, у нас есть foo
с одним аргументом, семантически определенный как
int foo(int arg) {
int* parg;
if (arg != 5) {
parg = &arg;
}
return *parg;
}
Весь приведенный выше код используется для иллюстрации простой идеи - функция возвращает собственный аргумент, если аргумент не равен 5, в этом случае поведение не определено.
Теперь задача - изменить функцию таким образом, чтобы, если ее аргумент был известен во время компиляции, генерировалась диагностика компилятора (предупреждение или ошибка), а если нет, поведение оставалось неопределенным во время выполнения. Решение может зависеть от компилятора, если оно доступно в одном из 4 больших компиляторов.
Вот некоторые потенциальные маршруты, которые не решают проблему:
- Делать функцию шаблоном, который принимает ее аргумент в качестве параметра шаблона - это не решает проблему, потому что делает функцию непригодной для аргументов времени выполнения
- Создание функции
constexpr
- это не решает проблему, потому что даже когда компиляторы видят неопределенное поведение, они не производят диагностику в моих тестах - вместо этого, gcc вставляет инструкцию ud2
, а это не то, что мне нужно.
Ответы
Ответ 1
Я получил ошибку с constexpr
при использовании в константном выражении для:
constexpr int foo(int arg) {
int* parg = nullptr;
if (arg != 5) {
parg = &arg;
}
return *parg;
}
демонстрация
Мы не можем знать, что значение аргумента известно при типе компиляции, но мы можем использовать тип, представляющий значение, с помощью std::integral_constant
// alias to shorten name.
template <int N>
using int_c = std::integral_constant<int, N>;
Возможно, с UDL с operator "" _c
будет 5_c
, 42_c
.
и затем добавьте перегрузку с этим:
template <int N>
constexpr auto foo(int_c<N>) {
return int_c<foo(N)>{};
}
Так:
foo(int_c<42>{}); // OK
foo(int_c<5>{}); // Fail to compile
// and with previous constexpr:
foo(5); // Runtime error, No compile time diagnostic
constexpr auto r = foo(5); // Fail to compile
Как я уже сказал, аргументы, как известно, не являются постоянными внутри функции, и is_constexpr
в стандарте не позволяет разрешить диспетчеризацию, но некоторые компиляторы предоставляют встроенные для этого (__builtin_constant_p
), поэтому с MACRO мы можем выполнить диспетчеризацию:
#define FOO(X) [&](){ \
if constexpr (__builtin_constant_p(X)) {\
return foo(int_c<__builtin_constant_p (X) ? X : 0>{});\
} else {\
return foo(X); \
} \
}()
демонстрация
Примечание. Невозможно использовать foo(int_c<X>{})
напрямую, даже если используется constexpr, так как все еще существует некоторая проверка синтаксиса.
Ответ 2
Компиляторы gcc/clang/intel поддерживают __builtin_constant_p, поэтому вы можете использовать что-то вроде этого:
template <int D>
int foo_ub(int arg) {
static_assert(D != 5, "error");
int* parg = nullptr;
if (arg != 5) {
parg = &arg;
}
return *parg;
}
#define foo(e) foo_ub< __builtin_constant_p(e) ? e : 0 >(e)
эти операторы приводят к ошибке времени компиляции:
-
foo(5)
-
foo(2+3)
-
constexpr int я = 5; foo(i);
в то время как все остальные - время выполнения segfault (или ub, если не используется nullptr
)
Ответ 3
Он не идеален и требует использования аргументов в двух разных местах, но он "работает":
template<int N = 0>
int foo(int arg = 0) {
static_assert(N != 5, "N cannot be 5!");
int* parg;
if (arg != 5) {
parg = &arg;
}
return *parg;
}
Мы можем назвать это так:
foo<5>(); // does not compile
foo(5); // UB
foo<5>(5); // does not compile
foo<5>(10); // does not compile
foo<10>(5); // UB
foo(); // fine
foo<10>(); // fine
foo(10); // fine