narrowing conversion в constexpr выражении
Есть функция, которая получает параметры через универсальные ссылки, затем инициализирует std::array этими параметрами
template<typename T, typename... Args>
concept BraceEnclosedInitializableFrom = requires(Args&&... args)
{
T{std::forward<Args>(args)...};
};
template<typename T, typename... Args>
requires BraceEnclosedInitializableFrom<std::array<T, 5>, Args...>
void foo(Args&&... args)
{
[[maybe_unused]] std::array<T, 5> arr{std::forward<Args>(args)...};
}
При вызове функции:
void call()
{
//foo<uint32_t>(1u, 2u, 3u, 4u, 5u); //работает
//foo<std::string>("1","2","3","4","5"); //работает
foo<uint32_t>(1, 2, 3, 4, 5); //не работает
}
При том, если закомментировать строку:
//requires BraceEnclosedInitializableFrom<std::array<T, 5>, Args...>
То вызов функции, триггерящий narrowing conversion, магическим образом начинает компилироваться (но только с GCC, clang выдаёт такую же ошибку).
Это можно починить, если немного переделать функцию, добавив static_cast
template<typename T, typename... Args>
// requires BraceEnclosedInitializableFrom<std::array<T, 5>, Args...>
void foo(Args&&... args)
{
[[maybe_unused]] std::array<T, 5> arr{static_cast<T>(std::forward<Args>(args))...};
}
Но это, как мне кажется, плохое решение, поскольку:
- Это уже не perfect forwarding (Я привожу универсальную ссылку к нессылочному типу
T - Непонятно, как переделать
concept BraceEnclosedInitializableFrom
Пожалуйста, подскажите, как починить код, чтобы можно было:
- Инициализировать
std::arrayчем угодно, что в него передают (сейчас использую для этого perfect forwarding) - Работал концепт
BraceEnclosedInitializableFrom(есть другие экземпляры функцииfoo(), которые иначе может быть невозможно вызвать)
Ответы (1 шт):
Помогло мне немного погуглить и подумать
В C++ ссылочный параметр функции не является constexpr выражением
void foo(const int& val) {
constexpr int a = val; // error: ‘val’ is not a constant expression
}
void call() {
foo(1);
}
Это важно, поскольку List-initialization разрешает narrowing conversion только в том случае, если значение является constexpr выражением + после конвертации значение помещается в приводимый тип.
Поскольку foo() принимает аргументы по (универсальной) ссылке
void foo(Args&&... args)
агрегатная инициализация массива, требующая конвертацию, просто не сработает.
std::array<T, 5> arr{std::forward<Args>(args)...};
Решить эту дилемму можно разными способами. Поскольку понятие narrowing conversion имеет смысл только в отношении арифметических типов, а perfect forwarding наоборот, имеет мало смысла при работе с ними, то можно сделать отдельный экземпляр foo(), принимающий исключительно числа, конвертирующий их в нужный тип и только затем инициализирующий массив.
Но, лучшее решение проблемы - самое простое:
void call() {
foo<uint32_t>(1u, 2u, 3u, 4u, 5u); //работает, не трогай
}