LINUX.ORG.RU

Clang или GCC кто неправ?

 , , ,


0

10
#include <array>
#include <cmath>
#include <iostream>

using namespace std;

template <typename T, int Size>
class Vector {
public:
	Vector() = default;
	~Vector() = default;

	// gcc - срабатывает static_assert (которые перед main-ом)
	// clang - все нормально
//	template <typename... Args>
//	Vector(const Args... args)
//			: values { args... } {
//		static_assert(sizeof...(args) == Size, "Vector::Vector -- wrong number of arguments");
//	}

	// Оба компилятора считают это, хорошим, годным кодом.
	template<int S, typename... Args>
	Vector(const Vector<T, S>& vector, const Args... args) {
		static_assert((sizeof...(args) + S) == Size, "Vector::Vector -- wrong number of arguments");

		// Говнокод, но не суть
		const array<T, sizeof...(args)> tmp { args... };

		copy(vector.values.begin(), vector.values.end(), values.begin());
		copy(tmp.begin(), tmp.end(), values.begin() + S);
	}

private:
	array<T, Size> values;
};

using Vector2f = Vector<float, 2>;
// вот этот
static_assert(is_pod<Vector2f>::value, "Vector<T, Size> should be a POD");

int main() {
	return 0;
}

Вопрос в том, чем этот конструктор такой особенный? Ну и что делать, да.

Вроде если класс удовлетворяет списку ниже, то он POD.

  • Has a trivial default constructor. This may use the default constructor syntax (SomeConstructor() = default;).
  • Has trivial copy and move constructors, which may use the default syntax.
  • Has trivial copy and move assignment operators, which may use the default syntax.
  • Has a trivial destructor, which must not be virtual.
  • It has no virtual functions
  • It has no virtual base classes
  • All its non-static data members have the same access control (public, private, protected)
  • All its non-static data members, including any in its base classes, are in the same one class in the hierarchy
  • The above rules also apply to all the base classes and to all non-static data members in the class hierarchy
  • It has no base classes of the same type as the first defined non-static data member

То есть gcc-ня приняла этот конструктор за конструктор копирования/перемещения?

gcc 4.8.2 (Ubuntu 4.8.2-19ubuntu1)
clang 3.4-1ubuntu3
★★★

Потыкал тут: http://gcc.godbolt.org/

clang-и все компилируют, кроме 3.0 (старее).

gcc 4.9.0 не компилирует.

icc 13.0.1 не компилирует. Притом он не компилирует даже то, что компилирует gcc. Что бы он скомпилировал, нужно убрать все конструкторы и private: (похоже на требования к POD до c++11).

Kuzy ★★★ ()

Лол, я всех обманул.

#include <iostream>

using namespace std;

class A {
public:
	A() = default;
	~A() = default;
	
	template <typename... Args>
	A(Args&... args) {
		cout << "copy!" << endl;
	}
};

static_assert(is_pod<A>::value, "RRRrrr!1");

void foo(A a) {}

int main() {
	A a;
	A b(a);
	
	foo(a);
	
	return 0;
}

gcc не компилирует (static_assert).

clang компилирует и выводит.... !!!

copy!
copy!

То есть конструктор копирования не тривиальный. При этом ассерт не сработал.

Kuzy ★★★ ()
Ответ на: комментарий от Kuzy

Похоже на баг в clang - конструктор копирования не может быть шаблоном.

Begemoth ★★★★★ ()
Ответ на: комментарий от Begemoth

а почему конструктор копирования-то? он же не A(A&). Это он только в частном случае конструктор копирования

anonymous ()
Ответ на: комментарий от Begemoth

Непонятно, на самом деле, у конструктора копирования же нет специального модификатора (so c++), значит любой конструктор с A(A&) - конструктор копирования, или что должно было произойти? Он не должен его специализнуть что ли?

Если убрать static_assert, то и gcc будет вести себя также.

Ожидаемо чинится добавлением конструктора копирования:

class A {
public:
	A() = default;
	~A() = default;
	A(A&) = default;
	
	template <typename... Args>
	A(Args&... args) { // не специализируется на {A}
		cout << "copy!" << endl;
	}
};

Kuzy ★★★ ()
Ответ на: комментарий от Kuzy

Хотя после ввода редукции & и &&, в языке который получается после раскрытия шаблонов, такая логика становится не совсем верна.

Kuzy ★★★ ()
Ответ на: комментарий от anonymous

О чём и речь, компилятор должен был сгенерировать конструктор копирования по-умолчанию, а не специализировать template <typename... Args> A(Args&... args).

Begemoth ★★★★★ ()
Ответ на: комментарий от Kuzy

значит любой конструктор с A(A&) - конструктор копирования, или что должно было произойти?

Любой не шаблонный конструктор, первый аргумент которого является ссылкой на A (A&, A const&, A volatile& или A const volatile&) и не имеет более аргументов или для всех остальных аргументов заданы значения по-умолчанию.

Он не должен его специализнуть что ли?

Нет.

Begemoth ★★★★★ ()
Ответ на: комментарий от Begemoth

Действительно, то есть в gcc и clang один и тот же баг (стандарт 12.8.2). Сильно.

Kuzy ★★★ ()
Ответ на: комментарий от Kuzy

Извиняюсь, похоже я не учёл одну деталь: при copy initialization (это в частности передача аргументов и возврат значений) необязательно вызывается конструктор копирования, а выполняется полноценное разрешение перегрузки и тут шаблонный конструктор может иметь сигнатуру которая лучше подходит списку аргументов.

Begemoth ★★★★★ ()
Последнее исправление: Begemoth (всего исправлений: 2)
Ответ на: комментарий от Begemoth

The initialization that occurs in the form

T x = a;
as well as in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and aggregate member initialization (8.5.1) is called copy-initialization.

И далее в контексте copy-initialization и direct-initialization.

The applicable constructors are enumerated (13.3.1.3), and the best one is chosen through overload resolution (13.3).

Что-то я не понял, а зачем тогда нужен конструктор копирования? Точнее зачем в стандарте для него прописаны всякие требования вроде не-темплейтности. Получается что он не на что не влияет же?

Kuzy ★★★ ()
Ответ на: комментарий от Kuzy

Точнее я не понял, является ли специализация

template <typename... Args>
A(Args&... args) 
по {A} конструктором копирования.

Kuzy ★★★ ()
Ответ на: комментарий от Kuzy

For copy-initialization, the candidate functions are all the converting constructors (12.3.1) of that class.

A constructor declared without the function-specifier explicit specifies a conversion from the types of its parameters to the type of its class. Such a constructor is called a converting constructor.

#include <iostream>

using namespace std;

class A {
public:
	A() = default;
	~A() = default;
	
	template <typename... Args>
	explicit A(Args&... args) {
		cout << "copy!" << endl;
	}
};

void foo(A a) {
}


int main() {
	A a;
	A b(a);
	cout << 1 << endl;
	A c = a;
	
	foo(a);

	return 0;
}

Выводит:

copy!
1

Kuzy ★★★ ()
Ответ на: комментарий от Kuzy
A b(a);

Это direct-initialization. Она, короче, хавает все конструкторы. Такие дела.

For direct-initialization, the candidate functions are all the constructors of the class of the object being initialized.

Kuzy ★★★ ()
Ответ на: комментарий от Kuzy

Что-то я нифига, все равно, не пойму.

В описании copy-constructor-ов...

A class object can be copied or moved in two ways: by initialization (12.1, 8.5), including for function argument passing (5.2.2) and for function value return (6.6.3); and by assignment (5.17). Conceptually, these two operations are implemented by a copy/move constructor (12.1) and copy/move assignment operator (13.5.3).

То есть, копирование «by initialization, including for function argument passing and for function value return» предполагается должно делаться с помощью конструктора копирования («Conceptually, these two operations are implemented by a copy/move constructor»). А для него есть требования не темплейтности («A non-template constructor for class X is a copy constructor if...»).

Но при этом, в другой части стандарта написано о «copy-initialization», которая выбирает конструкторы с помощью «overload resolution» («and the best one is chosen through overload resolution»).

А overload resolution, для copy-initialization, выбирает converting-constructor-ы, а не copy-constructor-ы.

То есть то, что написано в стандарте о copy-constructor-ах никого не касается? Просто на будущее задефайнили?

Kuzy ★★★ ()
Ответ на: комментарий от LongLiveUbuntu

C++ превращается в PL/I. Просто отлично.

Дык, большинство развивающихся языков это ждёт? Вон в С# тоже всё добавляют и добавляют всякого. Даже джава на месте не стоит.

В С++ «просто» больше «опасных граблей» успело скопиться.

DarkEld3r ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.