Давайте возьмем вот такие флаги компиляции
--std=c++2c -O2 -pedantic -Wall -Wextra -fsanitize=address -fsanitize=undefined
Для icc сделаем
-std=c++20, потому что 2c он не знает. Стартовый пример, который хотел показать.
Ни одного ворнинга на компиляторах не выдает.
Что в этом примере:
void foo() { // never called
if constexpr(false) { // never true
if (false) { // never true
constexpr auto call = [](auto arg) {
std::printf("called %d", arg);
};
void(B<A<tag>, decltype(call)>{});
}
}
}
Самая обычная функция, которая нигде в коде не вызывается. Внутри функции
if constexpr (false) который какбы тоже не должен никогда вызваться. Я бы вообще ожидал что блок внутри выкенется из компиляции. Внутри этого блока if (false). Внутри котого лямбда (причем с аргументом и локальной переменной). Внимание, вопрос! А можно ли как-то вызвать эту лямбду?
Оказывается да, и нам в этом помогает вот такая строчка:
void(B<A<tag>, decltype(call)>{});
Что за классы такие А и В? А вот как они объявляются:
class tag;
template<class>
struct A {
template<class>
friend constexpr auto get(A);
};
template<class K, class V>
struct B {
template<class>
friend constexpr auto get(K) { return V{}; }
};
И вуаля, теперь мы можем вызвать эту функцию вот таким кодом:
int main() {
get<tag>(A<tag>{})(42);
}
Много раз повторив добьемся того же эффекта. И ни одного ворнинга.
Да, объявив лямбду вот так:
constexpr auto call = [&](auto arg) {
std::printf("called %d", arg);
};
Получим веселую ошибку компиляции:
note: a lambda closure type has a deleted default constructor
(Да, я просто добавил `[&]`).
И я изначально, встретив нечно похожее, шел по пути усложнения кода. Потому что хотел избавиться от всех ворнингов, а, например, закомментрируем template в объявлении функции get, и хотябы gcc начнет сыпать хоть какими-то ворнингами:
template<class>
struct A {
// template<class>
friend constexpr auto get(A);
};
template<class K, class V>
struct B {
// template<class>
friend constexpr auto get(K) { return V{}; }
};
warning: friend declaration 'constexpr auto get(A< <template-parameter-1-1> >)' declares a non-template function
А
icc так вообще перестанет собирать код:
internal error: assertion failed at: "func_def.c", line 1915 in scan_function_body
get(A<tag>{})(42);
Но как оказалось, я был не прав, и идти надо по пути упрощения. Потому что
#include <cstdio>
struct A {
friend auto get(A);
};
template<class V>
struct B {
friend auto get(A) { return V{}; }
};
void foo() { // never called
if constexpr(false) { // never true
if (false) { // never true
constexpr auto call = [](auto arg) {
std::printf("called %d", arg);
};
void(B<decltype(call)>{});
}
}
}
int main() {
get(A{})(42);
}
тоже прекрасно работает.
Вот ей богу, я эту грязь знать не хотел. 🤢