Шаблонный constexpr метод с переменным числом параметров

Есть вот такой код

#include <iostream>
#include <type_traits>

template <typename T>
constexpr T ThereNegative(const T& arg) {
    if (arg < 0) return arg;
    return 0;
}

template <typename F, typename ...Rest>
constexpr std::common_type_t<F, Rest...> ThereNegative(const F& first_arg, const Rest&... args) {
    if (first_arg < 0) return first_arg;
    return ThereNegative(args...);
}

template <typename ...T>
constexpr bool foo(const T& ...args) {
    static_assert(ThereNegative(args...) == 0, "Negative argument.");
    return true;
}

int main()
{
    //foo(2, 4lu, char(5), 0, 3);

    constexpr auto AAA = ThereNegative(2, 4lu, char(5), 0, 3);
    constexpr auto BBB = ThereNegative(2, 4lu, char(-5), 0, 3);

    std::cout << AAA << std::endl;
    std::cout << BBB << std::endl;
    return 0;
}

Он прекрасно работает и выводит ожидаемый результат

0
4294967291 (эта та самая -5)

Но когда я снимаю комментарий со строки

foo(2, 4lu, char(5), 0, 3);

вылазит ошибка компиляции

18:20:31: Выполняются этапы для проекта test_static_assert...
18:20:31: Запускается: «C:\Qt\Tools\CMake_64\bin\cmake.exe» --build D:/QT_PRO/build-test_static_assert-Desktop_Qt_6_6_0_MinGW_64_bit-Debug --target all
[1/2 3.3/sec] Building CXX object CMakeFiles/test_static_assert.dir/main.cpp.obj
FAILED: CMakeFiles/test_static_assert.dir/main.cpp.obj 
C:\Qt\Tools\mingw1120_64\bin\g++.exe   -DQT_QML_DEBUG -g -std=gnu++17 -MD -MT CMakeFiles/test_static_assert.dir/main.cpp.obj -MF CMakeFiles\test_static_assert.dir\main.cpp.obj.d -o CMakeFiles/test_static_assert.dir/main.cpp.obj -c D:/QT_PRO/test_static_assert/main.cpp
D:/QT_PRO/test_static_assert/main.cpp: In instantiation of 'constexpr bool foo(const T& ...) [with T = {int, long unsigned int, char, int, int}]':
D:/QT_PRO/test_static_assert/main.cpp:24:31:   required from here
D:/QT_PRO/test_static_assert/main.cpp:18:42: error: non-constant condition for static assertion
   18 |     static_assert(ThereNegative(args...) == 0, "Negative argument.");
      |                   ~~~~~~~~~~~~~~~~~~~~~~~^~~~
D:/QT_PRO/test_static_assert/main.cpp:18:42: error: 'args#0' is not a constant expression
D:/QT_PRO/test_static_assert/main.cpp:18:42: error: 'args#1' is not a constant expression
D:/QT_PRO/test_static_assert/main.cpp:18:42: error: 'args#2' is not a constant expression
D:/QT_PRO/test_static_assert/main.cpp:18:42: error: 'args#3' is not a constant expression
D:/QT_PRO/test_static_assert/main.cpp:18:42: error: 'args#4' is not a constant expression
ninja: build stopped: subcommand failed.
18:20:32: Процесс «C:\Qt\Tools\CMake_64\bin\cmake.exe» завершился с кодом 1.
Ошибка при сборке/развёртывании проекта test_static_assert (комплект: Desktop Qt 6.6.0 MinGW 64-bit)
Во время выполнения этапа «Собрать»
18:20:32: Прошло времени: 00:00.

Что именно тут происходит? Почему внутри метода ThereNegative constant expression не теряется, а внутри foo она потерялась?


Ответы (2 шт):

Автор решения: sibedir

Вроде бы, разобрался.

https://ru.cppreference.com/w/cpp/language/static_assert

static_assert должен содержать bool-constexpr или константные выражения типа bool или преобразованное константное выражение типа bool. Ни то ни другое в контексте аргументов метода не возможно.

Вот такой код не компилируется

#include <iostream>

constexpr bool ItsLike(bool val) {
    static_assert(val, "It's not like that");
    return true;
}

int main()
{
    std::cout << std::boolalpha << ItsLike(0 == 0); << std::endl;
    return 0;
}

Пока наиболее близкое решение, что я нашёл, предоставляет С++20. В нём есть возможность создавать constexpr методы, содержащие try. Немного костыльно, но лучше мне пока придумать не удалось

#include <iostream>
#include <type_traits>

template <typename T>
constexpr void TryArePositive(const T& arg) {
    if (arg < 0) throw std::invalid_argument("Negative argument.");
}

template <typename F, typename ...Rest>
constexpr void TryArePositive(const F& first_arg, const Rest&... args) {
    if (first_arg < 0) throw std::invalid_argument("Negative argument.");
    TryArePositive(args...);
}

template <typename ...Ts>
constexpr bool ArePositive(const Ts&... args) {
    try {
        TryArePositive(args...);
        return true;
    } catch (...) {
        return false;
    }
}

template <typename ...T>
constexpr bool foo2(const T& ...args) {
    return ArePositive(args...);
}

int main()
{
    constexpr auto b1 = foo2(2, 4lu, char(5), 0, 3); // OK
    constexpr auto b2 = foo2(2, 4lu, char(-5), 0, 3); // ERROR - как и хотелось

    std::cout << std::boolalpha << b1 << std::endl;
    return 0;
}
→ Ссылка
Автор решения: sibedir

Спасибо за подсказку от @user7860670. Не знал, что можно делать template <auto x>. Действительно, это подходит, если нужны действия чисто в compile-time.

template <auto arg>
constexpr void AssertArePositive() {
    static_assert(arg >= 0, "Negative argument.");
}

template <auto first, auto... rest>
    requires (sizeof...(rest) > 0)
constexpr void AssertArePositive() {
    AssertArePositive<first>();
    AssertArePositive<rest...>();
}

int main()
{
    AssertArePositive<2, 4lu, char(5), 1, 3>();
    AssertArePositive<2, 4lu, char(-5), 1, 3>();
    return 0;
}

Добавлено:

Позору моему нет предела. Спасибо за подсказку от @user7860670. Не знал, что раскрывать пакеты можно еще проще.

template <auto... args>
constexpr void AssertArePositive() {
    static_assert(((args >= 0) and ...), "Parameters must be positive.");
}

int main()
{
    AssertArePositive<2, 4lu, char(5), 1, 3>();
    AssertArePositive<-2, 4lu, char(5), 1, 3>();
    return 0;
}
→ Ссылка