Подтвердить что ты не робот

Возвратный переменный конструктор - почему это работает?

Отвечая на этот вопрос о попытке построить вариационный конструктор ссылок пересылки, который должен вызываться только в том случае, если не существует другого конструктора. То есть, если было:

C(const char*, size_t) { }                     // 1
template <typename... T, ???> C(T&&... ) { }   // 2

Мы хотим, чтобы C c1{"abc", 2}; вызывал (1), несмотря на требуемое преобразование, но C c2{1, 2, 3}; для вызова (2), поскольку (1) не может применяться.

Я предложил следующее решение:

template <typename... T,
          typename = std::enable_if_t<!std::is_constructible<C, T&&...>::value>
           >
C(T&&... ) { }

И предлагаемый, я имею в виду, я попробовал это и с удивлением обнаружил, что он действительно работает. Он компилирует и делает то, что я надеялся на gcc и clang. Тем не менее, я затрудняюсь объяснить, почему он работает, или даже если он действительно должен работать, и gcc и clang оба просто приспосабливаются. Это? Зачем?

4b9b3361

Ответ 1

Проблема с вашим кодом заключается в том, что мы просто создали экземпляр is_constructible в контексте, где он получает ответ неправильно. Любое кэширование кода шаблона может привести к ошибкам - попробуйте распечатать is_constructible по тем же параметрам после вызова конструктора! Вероятно, это ошибочно.

Живой пример, как это может пойти не так. Обратите внимание, что претензии C не могут быть построены из int&, несмотря на то, что сделали это на предыдущей строке.

struct C {
  C(const char*, size_t) {}
  template <class... Ts,
    typename = std::enable_if_t<!std::is_constructible<C, Ts&&...>::value>
  >
  C(Ts&&... ) { }
};

int main() {
  int a = 0;
  C x{a};
  std::cout << std::is_constructible<C, int&>{} << '\n';
}

упс.

Я подозреваю, что это может быть нарушение ODR - два определения is_constructible имеют разные типы в разных местах? Или, может быть, нет.

Также опубликовано решение исходной проблемы, которая не имеет этой проблемы.