Неверный результат для std::is_tribuly_constructible‹T, T&›::value при объявлении T T::T(T&)

Это небольшая проблема, с которой я запутался. Я не знаю, как это описать, поэтому просто посмотрите коды ниже:

struct B {
  B() {}
  B(B&) {
    std::cout << "not trivial\n";
  }
};

int main() {
  B b1;
  B b2(b1);
  std::cout << std::is_trivially_constructible<B,  B&>::value << '\n';
  return 0;
}

Результат:

not trivial
1

Я использую VS11.

ИЗМЕНИТЬ:

Я только что проверил пример в http://en.cppreference.com/w/cpp/types/is_constructible. Часть вывода неверна.

#include <iostream>
#include <type_traits>
 
class Foo {
    int v1;
    double v2;
 public:
    Foo(int n) : v1(n), v2() {}
    Foo(int n, double f) : v1(n), v2(f) {}
};
int main() {
    std::cout << "Foo is ...\n" << std::boolalpha
              << "\tTrivially-constructible from const Foo&? "
              << std::is_trivially_constructible<Foo, const Foo&>::value << '\n'
              << "\tTrivially-constructible from int? "
              << std::is_trivially_constructible<Foo, int>::value << '\n'
              << "\tConstructible from int? "
              << std::is_constructible<Foo, int>::value << '\n'
}

Результат:

Foo is ...
        Trivially-constructible from const Foo&? true
        Trivially-constructible from int? true//Trivially-constructible from int? false
        Constructible from int? true

person Frahm    schedule 26.04.2013    source источник
comment
Прочитав и перечитав то, что стандарт должен сказать об этом несколько раз (и дважды удалив свой первый ответ), я считаю, что наконец нашел причину этой путаницы. Смотрите мой ответ.   -  person Agentlien    schedule 26.04.2013
comment
После комментария @SebastianRedl я обновил свой ответ снова (вздох)..   -  person Agentlien    schedule 26.04.2013


Ответы (2)


ФИНАЛЬНОЕ ОБНОВЛЕНИЕ

После очень проницательного комментария @SebastianRedl я понял, что целью стандарта является обращение ко всей конструкции объекта, а не только к операциям внутри конструктора. Это означало бы, что в Visual C++ действительно есть ошибка. Тем не менее, я по-прежнему считаю, что формулировка стандарта недостаточно ясна, поэтому я оставлю остальную часть своего ответа без изменений для потомков.

Чтобы уточнить: поведение, упомянутое в OP, на самом деле является ошибкой, и в свете этого большая часть того, что я говорю ниже этого обновления, неверно.

Завершить обновление

На самом деле это не ошибка компилятора, а скорее странная особенность стандарта, поэтому ваше замешательство понятно.

Согласно стандарту C++11, следующее условие для того, чтобы is_trivially_constructible::value было true.

§20.9

is_constructible<T,Args...>::value истинно, и известно, что определение переменной для is_constructible, как определено ниже, не вызывает никакую операцию, которая не была бы тривиальной.

Таким образом, is_trivially_constructible верно, пока данный тип можно построить с заданными аргументами и он не вызывает никаких нетривиальных операций. В вашем примере есть только один такой конструктор — конструктор копирования. На самом деле, по определению «нетривиальной операции» (по сути, нетривиального оператора или конструктора) это справедливо для вашего типа. Так что возвращение true правильно.

Однако есть один очень странный момент! Стандарт C+11 говорит о конструкторах копирования следующее:

§12.8.12 (выделено мной)

Конструктор копирования/перемещения для класса X тривиален, если он не предоставляется пользователем и если

  • класс X не имеет виртуальных функций (10.3) и виртуальных базовых классов (10.1), и
  • конструктор, выбранный для копирования/перемещения каждого прямого подобъекта базового класса, тривиален, и
  • для каждого нестатического члена данных X, который имеет тип класса (или его массив), конструктор, выбранный для копирования/перемещения этого члена, является тривиальным;

    в противном случае конструктор копирования/перемещения нетривиален.

Поскольку вы предоставляете пользовательский конструктор копирования, ваш класс не является тривиально копируемым. Указанный вами конструктор копирования не тривиален. Тем не менее, нетривиальный конструктор копирования выполняет необходимые условия для того, чтобы is_trivially_constructible возвращал true с аргументом, который соответствует вашему конструктору копирования.

На мой взгляд, это больше похоже на «ошибку» в стандарте. is_trivially_constructible возвращает, является ли тип тривиально конструируемым с учетом определенных аргументов. Похоже, это не гарантирует, что сам конструктор считается тривиальным!

Обновление:

После попытки разработать тест, демонстрирующий следующий случай, я обнаружил ошибку в VC11. Логика, описанная стандартом, означает, что если B используется в качестве подобъекта (члена или базы) другого типа, любой конструктор этого типа, который вызывает конструктор копирования B, должен считаться нетривиальным от std::is_trivially_constructible. В VC11 такого нет.

Пример кода

#include <iostream>
#include <type_traits>

struct B 
{
  B() {}
  B(B&) {
    std::cout << "not trivial\n";
  }
};

struct A : B
{
  A(B& B) : b(B){}
    B b;
};

int main() 
{
  std::cout << std::is_trivially_constructible<B,  B&>::value << '\n'; // Should print 1
  std::cout << std::is_trivially_constructible<A,  B&>::value << '\n'; // Should print 0
  getchar();
  return 0;

}

person Agentlien    schedule 26.04.2013
comment
@arne Спасибо. :) Я приложил к этому много усилий. - person Agentlien; 26.04.2013
comment
Я не знаю, что вы имеете в виду под нетривиальной операцией (по сути, нетривиальным оператором или конструктором), но даже если я вызову виртуальную функцию внутри этого копирующего ctor, результат все равно будет 1, и посмотрите мой РЕДАКТИРОВАТЬ, вывод отличается от указанного в en.cppreference.com/w/cpp/types/is_constructible - person Frahm; 26.04.2013
comment
@Frahm Определение того, что делает оператор (например, присваивание) или конструктор нетривиальным, распространяется по всему стандарту. Однако вызов виртуальной функции не делает ваш конструктор нетривиальным. Это можно сделать только при вызове других функций, которые считаются нетривиальными. Итак, если вы не вызываете нетривиальный оператор присваивания или не указываете нетривиальный конструктор в своем списке инициализаторов, ваш конструктор тривиален. - person Agentlien; 26.04.2013
comment
Итак, каково определение нетривиального оператора присваивания, или вы указываете нетривиальный конструктор? это похоже на определение рекурсии. - person Frahm; 26.04.2013
comment
@Frahm Это рекурсивно, и единственный способ, которым конструктор может быть нетривиальным, - это если 1) список инициализации включает нетривиальный конструктор копирования. 2) Элемент или база инициализируется с использованием нетривиального конструктора по умолчанию. 3) Тело конструктора вызывает нетривиальный оператор присваивания. Любой оператор присваивания или конструктор копирования/перемещения/по умолчанию нетривиален, если он предоставляется пользователем. Итак, в представленном вами выводе нет ошибки. Однако я обнаружил тесно связанную ошибку в VC11! Смотрите мое обновление. - person Agentlien; 26.04.2013
comment
Я не понимаю первую часть вашего ответа. Почему вы утверждаете, что конструкция, определенная в 20.9.4.3p6, не использует никаких нетривиальных операций? Он использует нетривиальный конструктор копирования. - person Sebastian Redl; 26.04.2013
comment
@SebastianRedl Это очень хороший момент. Я интерпретировал это так, что вызываемый конструктор не использует никаких нетривиальных операций. Но если вместо этого интерпретировать его как конструкцию в целом, то да, он использует нетривиальные операции (сам копирующий-конструктор). В любом случае, формулировка в стандарте довольно расплывчата и открывает возможности для различных интерпретаций или легкого недопонимания. Как насчет того, чтобы я отредактировал свой ответ, чтобы он содержал примечание об этом вверху? - person Agentlien; 26.04.2013
comment
@SebastianRedl После повторного прочтения части о is_constructible становится ясно, что на самом деле это следует считать нетривиальным. Я обновлю, добавлю примечание к ответу. - person Agentlien; 26.04.2013

Из http://en.cppreference.com/w/cpp/types/is_constructible:

«Выражение конструктора не вызывает никаких операций, которые не являются тривиальными». и это тот случай, когда вы пишете

B(B&);

там ничего особенного не происходит, вы просто передаете ссылку.

person arne    schedule 26.04.2013
comment
тривиальные средства не объявляются, поэтому я думаю, что вывод неверен. - person Frahm; 26.04.2013
comment
Насколько я понимаю, указанная строка просто означает, что оператору с выражением конструктора, т. е. оператору, вызывающему конструктор, не нужно вызывать какие-либо другие нетривиальные операции, такие как вычисления, которые нельзя выполнить во время компиляции или вызовы функций. - person arne; 26.04.2013
comment
Я думаю, что операция std::cout ‹‹ нетривиальна\n; нельзя сделать во время компиляции. - person Frahm; 26.04.2013