Параметр constexpr-function считается constexpr, если он используется напрямую, но не используется для вызова другой функции constexpr

Во время экспериментов с функциями constexpr и шаблонами (и аргументами шаблона непигового типа) я наткнулся на явление, и я не могу понять, какое правило его вносит в действие.

Итак, мой вопрос в основном заключается в следующем: "Почему это происходит", в соответствии с правилами о constexpr-s. "this" заключается в следующем.

В одной из функций constexpr, если параметр используется напрямую, нет никаких проблем с этим параметром, используемым при вычислении времени компиляции. (строки примера 2)

Когда тот же параметр используется в качестве аргумента для другой constexpr-функции, компилятор жалуется, что это выражение (идентификатор параметра) не является constexpr. (пример строки 3)

Короче:

template <typename T> constexpr std::size size (T obj) { return obj.size(); }
template <typename T> constexpr auto sz1 (T obj) { return std::make_index_sequence< obj.size() > { }.size(); } // OK ...
template <typename T> constexpr auto sz2 (T obj) { return std::make_index_sequence< size(obj) > { }.size(); } // ERROR
  // "obj" is [suddenly] not a constexpr

Это происходит как с g++ - 4.9.1, так и с clang++ - 3.4.2.

Ниже приведена небольшая тестовая программа для быстрого и легкого экспериментирования.


#include <utility>
#include <array>
#include <iostream>

// utils
template <size_t N> using require_constexpr = std::make_index_sequence<N>;
template <typename...> constexpr void noop (void) { }

// size() wrappers
template <typename T> constexpr std::size_t size (T obj) { return obj.size(); }
template <typename T> constexpr auto sz1 (T obj) { return size(require_constexpr< obj.size() > { }); }
template <typename T> constexpr auto sz2 (T obj) { return size(require_constexpr< size(obj) > { }); }

int main0 (int, char**)
{
  constexpr auto const ar = std::array<int, 4u> { 4, 5, 6, 7 };

  // Check constexpr-computability of size(), sz1() and the expansion of sz2()
  noop<
    require_constexpr<
      size(require_constexpr< ar.size() > { }) + sz1(ar) +
      size(require_constexpr< size(ar)  > { })
    >
  >();

  // But this is an error
  // ERROR: "obj" is not a constexpr in sz2()
//noop< require_constexpr< sz2(ar) > >();

  return 0;
}

Изменить Вот относительный вывод компиляции.

лязг

 src/main1.cpp:12:87: error: non-type template argument is not a constant expression
     template <typename T> constexpr auto sz2 (T obj) { return size(require_constexpr< size(obj) > { }); }
                                                                                       ^~~~~~~~~
 src/main1.cpp:28:32: note: in instantiation of function template specialization 'sz2<std::array<int, 4> >' requested here
       noop< require_constexpr< sz2(ar) > >();
                                ^
 src/main1.cpp:12:92: note: read of non-constexpr variable 'obj' is not allowed in a constant expression
     template <typename T> constexpr auto sz2 (T obj) { return size(require_constexpr< size(obj) > { }); }
                                                                                            ^
 src/main1.cpp:12:92: note: in call to 'array(obj)'
 src/main1.cpp:12:49: note: declared here
     template <typename T> constexpr auto sz2 (T obj) { return size(require_constexpr< size(obj) > { }); }
                                            ^

НКУ

src/main1.cpp: In substitution of ‘template<long unsigned int N> using require_constexpr = std::make_index_sequence<N> [with long unsigned int N = size<std::array<int, 4ul> >(obj)]’:
src/main1.cpp:12:102:   required from ‘constexpr auto sz2(T) [with T = std::array<int, 4ul>]’
src/main1.cpp:28:38:   required from here
src/main1.cpp:12:102: error: ‘obj’ is not a constant expression
     template <typename T> constexpr auto sz2 (T obj) { return size(require_constexpr< size(obj) > { }); }
                                                                                                      ^
src/main1.cpp:12:102: note: in template argument for type ‘long unsigned int’ 

Ответ 1

Это выглядит как ошибка, связанная с тем, как два компилятора рассматривают конструкторы копирования, созданные компилятором.

Этот код компилируется с использованием clang и g++:

#include <utility>

// utils
template <std::size_t N> struct require_constexpr { constexpr std::size_t size() const { return N; } };
struct test { 
  constexpr std::size_t size() const { return 0; } 
  constexpr test() { }
  constexpr test(const test &) { }
};
template <typename...> constexpr void noop (void) { }

// size() wrappers
template <typename T> constexpr std::size_t size (T obj) { return obj.size(); }
template <typename T> constexpr auto sz1 (T obj) { return size(require_constexpr< obj.size() > { }); }
template <typename T> constexpr auto sz2 (T obj) { return size(require_constexpr< size(obj) > { }); }

int main (int, char**)
{
  constexpr auto const ar = test();

  // Check constexpr-computability of size(), sz1() and the expansion of sz2()
  noop<
    require_constexpr<
      size(require_constexpr< ar.size() > { }) + sz1(ar) +
      size(require_constexpr< size(ar)  > { })
    >
  >();

  noop< require_constexpr< sz2(ar) > >();

  return 0;
}

Но если мы изменим линию

constexpr test(const test &) { }

to

constexpr test(const test &) = default;

Затем он не компилируется ни (g++, clang), хотя нет никакой разницы между что два конструктора (test являются полностью пустым классом), а §12.8 [class.copy]/p13 утверждает, что

Если неявно определенный конструктор удовлетворяет требованиям конструктора constexpr (7.1.5), неявно заданного конструктор constexpr.

Кроме того, если неявный конструктор копирования не был constexpr, то объявление с явным значением по умолчанию с constexpr должно было привести к неправильной форме программы с требуемой диагностикой (§8.4.2 [dcl. fct.def.default]/р2):

Явно-дефолтная функция может быть объявлена ​​ constexpr только если он был бы неявно объявлен как constexpr.

Но оба компилятора (clang, g++) компилируют вторую версию кода, если второй noop закомментирован.

Ответ 2

Ключевое различие между sz1 и sz2 заключается в том, что sz1 передает адрес obj в функцию члена размера, что не является допустимым результатом константного выражения, но отлично подходит как промежуточный результат. sz2 выполняет преобразование lvalue- > rvalue для obj для перехода к функции размера, и поскольку obj не является константой, это делает выражение непостоянным.

T.C. Интересен вопрос о неявных и явных конструкторах. Источником разницы является то, что неявный тривиальный конструктор копии выполняет побитовое копирование, которое включает в себя копирование (непостоянного) байта заполнения, тогда как созданный пользователем конструктор копирования ничего не копирует. Но в стандарте говорится, что неявный конструктор выполняет поэтапную копию, поэтому к ним следует относиться одинаково.

Что неясно, должны ли они быть отвергнуты или оба приняты; строгое чтение 5.19 предполагает, что оба должны быть отклонены, поскольку оба включают преобразование lvalue- > rvalue для obj с использованием конструктора копирования. Я поднял эту проблему с помощью комитета С++.