Несмотря на то, что раньше я использовал такой код, и ясно, что у компилятора достаточно информации для работы, я действительно не понимаю, почему это компилируется:
template <class T, class I>
auto foo(const T& t, I i) {
return std::get<i>(t);
}
int main()
{
std::cerr << foo(std::make_tuple(3,4), std::integral_constant<std::size_t, 0>{});
return 0;
}
Пример в реальном времени: http://coliru.stacked-crooked.com/a/fc9cc6b954912bc5.
Кажется, работает как с gcc, так и с clang. Дело в том, что в то время как integral_constant имеет преобразование constexpr в сохраненное целое число, функции-члены constexpr неявно принимают объект как аргумент, и поэтому такая функция не может использоваться в контексте constexpr, если только объект мы вызываем функцию-член, которую можно рассматривать как constexpr.
Здесь i - аргумент, передаваемый foo, и поэтому i, безусловно, нельзя рассматривать как constexpr. Тем не менее, это так. Еще более простой пример:
template <class I>
void foo(I i) {
constexpr std::size_t j = i;
}
Это тоже компилируется, пока std::integral_constant<std::size_t, 0>{} передается foo.
Я чувствую, что мне не хватает чего-то очевидного в правилах constexpr. Есть ли исключение для типов без гражданства или что-то еще? (или, может быть, ошибка компилятора в двух основных компиляторах? Этот код работает на clang 5 и gcc 7.2).
Изменить: ответ был опубликован, но я не думаю, что этого достаточно. В частности, учитывая последнее определение foo, почему:
foo(std::integral_constant<std::size_t, 0>{});
Скомпилировать, но не:
foo(0);
Оба значения 0 и std::integral_constant<std::size_t, 0>{} являются постоянными выражениями.
Edit 2: Кажется, что это сводится к тому, что вызов функции члена constexpr даже на объекте, который не является константным выражением, сам по себе может рассматриваться как постоянное выражение, если this неиспользованными. Это воспринимается как очевидное. Я не считаю это очевидным:
constexpr int foo(int x, int y) { return x; }
constexpr void bar(int y) { constexpr auto x = foo(0, y); }
Это не компилируется, потому что y, переданный в foo, не является константным выражением. Это неиспользованное не имеет значения. Таким образом, полный ответ должен показать какой-то язык из стандарта, чтобы оправдать тот факт, что функция-член constexpr может использоваться как константное выражение даже на объекте с не константным выражением, если this не используется.