LINUX.ORG.RU

Валиден ли код?

 ,


0

4

№1:

#ifdef USING_VOLATILE
	#define VOLATILE volatile
#else
	#define VOLATILE
#endif

void f(const int&) {}
void f(const volatile int&) {}

using T = const VOLATILE int;

int main() { T i = 0; f(static_cast<T&&>(i)); }
$ g++ -xc++ -std=c++14 -pedantic-errors -o/dev/null -DUSING_VOLATILE test.cxx 
test.cxx: In function 'int main()':
test.cxx:12:25: error: invalid initialization of non-const reference of type 'const volatile int&' from an rvalue of type 'T {aka const volatile int}'
 int main() { T i = 0; f(static_cast<T&&>(i)); }
                         ^~~~~~~~~~~~~~~~~~~
test.cxx:8:6: note:   initializing argument 1 of 'void f(const volatile int&)'
 void f(const volatile int&) {}
      ^
№2:
#ifndef DIRECT_ALIASING
	#define ALIASED trait_t<As...>
#else
	#define ALIASED typename trait<As...>::type
#endif

template<typename T, typename... As> struct trait_impl { using type = T; };
template<typename... As> struct trait :trait_impl<As...> {};
template<typename T, typename... As> using trait_t = typename trait<T,As...>::type;
template<typename... As> using trait_type = ALIASED;

int main() { return trait_type<int,unsigned,long>(0); }
$ g++ -xc++ -std=c++14 -pedantic-errors -o/dev/null test.cxx 
test.cxx:2:31: error: pack expansion argument for non-pack parameter 'T' of alias template 'template<class T, class ... As> using trait_t = typename trait<T, As ...>::type'
  #define ALIASED trait_t<As...>
                               ^
test.cxx:10:45: note: in expansion of macro 'ALIASED'
 template<typename... As> using trait_type = ALIASED;
                                             ^~~~~~~
test.cxx:9:10: note: declared here
 template<typename T, typename... As> using trait_t = typename trait<T,As...>::type;
          ^~~~~~~~
test.cxx: In function 'int main()':
test.cxx:12:21: error: 'trait_type' was not declared in this scope
 int main() { return trait_type<int,unsigned,long>(0); }
                     ^~~~~~~~~~
test.cxx:12:32: error: expected primary-expression before 'int'
 int main() { return trait_type<int,unsigned,long>(0); }
                                ^~~
test.cxx:12:32: error: expected ';' before 'int'
test.cxx:12:35: error: expected unqualified-id before ',' token
 int main() { return trait_type<int,unsigned,long>(0); }
                                   ^
test.cxx:12:36: error: expected unqualified-id before 'unsigned'
 int main() { return trait_type<int,unsigned,long>(0); }
                                    ^~~~~~~~

Два бага в компиляторе за один день - это слишком толсто. Упорот ли я?

Deleted

№1

Похоже на баг.

№2

template <typename T, typename ...A>
using trait_t = void;

template<typename... As>
using trait_type = trait_t<As...>;
error: pack expansion used as argument for non-pack parameter of alias template

Факт наличия сообщения с упоминанием именно этого случая наводит на мысли, что это не баг.

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

non-pack parameter 'T'

Цимес в том, что я не пойму никак, где именно происходит распаковка T(не As). Можете немного прояснить ситуацию?

Deleted ()
Ответ на: комментарий от xaizek

№1

Похоже на баг.

А по-моему — нет, по-моему компилятор прав, что отвергает код.

http://eel.is/c++draft/dcl.init.ref#5

A reference to type “cv1 T1” is initialized by an expression of type “cv2 T2” as follows:

(5.1) If the reference is an lvalue reference and the initializer expression
(5.1.1) is an lvalue (but is not a bit-field), and “cv1 T1” is reference-compatible with “cv2 T2”, or
(5.1.2) has a class type (i.e., T2 is a class type), where T1 is not reference-related to T2, and can be converted to an lvalue of type “cv3 T3”, where “cv1 T1” is reference-compatible with “cv3 T3”108 (this conversion is selected by enumerating the applicable conversion functions ([over.match.ref]) and choosing the best one through overload resolution), then the reference is bound to the initializer expression lvalue in the first case and to the lvalue result of the conversion in the second case (or, in either case, to the appropriate base class subobject of the object). [ Note: The usual lvalue-to-rvalue, array-to-pointer, and function-to-pointer standard conversions are not needed, and therefore are suppressed, when such direct bindings to lvalues are done. — end note  ]
[ Example:

double d = 2.0; double& rd = d; // rd refers to d const double& rcd = d; // rcd refers to d

struct A { }; struct B : A { operator int&(); } b; A& ra = b; // ra refers to A subobject in b const A& rca = b; // rca refers to A subobject in b int& ir = B(); // ir refers to the result of B​::​operator int&

— end example  ]
(5.2) Otherwise, if the reference is an lvalue reference to a type that is not const-qualified or is volatile-qualified, the program is ill-formed.

В примере lvalue-reference, но инициализатор у неё не lvalue и не class type, значит (5.1) не подходит, смотрим в (5.2). А (5.2) говорит, что код со ссылками на volatile-qualified type — ill-formed.

sostupid ()
Ответ на: комментарий от Deleted

Нигде. Тут надо читать не так, меня тоже запутало сначала:

pack expansion         = As...
used as argument       = trait_t<As...>
for non-pack parameter = typename T
of alias template      = trait_t
Т.е. он не раскрывает As... для alias template, а пытается сопоставить аргументы и обнаруживает, что их «типы» не совпадают. Но я тоже не ожидал такого поведения.

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

Странно. Вот в таком случае

template<typename T, typename... As> struct trait { using type = T; };
template<typename... As> using trait_type = typename trait<As...>::type;

int main() { return trait_type<int,unsigned,long>(0); }
- работает, т. е. нужно именно не менее двух уровней using с отличающимися параметрами

Deleted ()
Ответ на: комментарий от sostupid

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

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

В примере lvalue-reference, но инициализатор у неё не lvalue и не class type, значит (5.1) не подходит, смотрим в (5.2). А (5.2) говорит, что код со ссылками на volatile-qualified type — ill-formed

У неё в обоих случаях инициализатор не lvalue, но ill-formed(по мнению компилятора) только в одном

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

В стандарте по alias template подобного не нашёл. Но попробовал clang и он выдаёт аналогичное сообщение об ошибке, так что наверное оно где-то есть в стандарте.

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

Но в обоих случаях есть const

(5.2) Otherwise, if the reference is an lvalue reference to a type that is not const-qualified or is volatile-qualified, the program is ill-formed.
[ Example:

double& rd2 = 2.0; // error: not an lvalue and reference not const
int i = 2;
double& rd3 = i; // error: type mismatch and reference not const

— end example
 ]

Для prvalue, ЧСХ, работает как и ожидалось. Проблема только с xvalue

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

Я объяснял про вторую перегрузку.

С первой-то вообще всё просто — она отбрасывает volatile и поэтому не подходит.

sostupid ()
Ответ на: комментарий от Deleted

Для prvalue, ЧСХ, работает как и ожидалось.

Потому что от если ты ему сунешь int prvalue (типа static_cast<volatile int>(0)), то у него отбросятся cv-квалификаторы:

http://eel.is/c++draft/expr#type-2

If a prvalue initially has the type “cv T”, where T is a cv-unqualified non-class, non-array type, the type of the expression is adjusted to T prior to any further analysis.

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

or is not volatile-qualified

Сцуко, я вот этот немаловажный момент невнимательно прочитал. Первый код в норме

Deleted ()
Ответ на: комментарий от xaizek

Похоже на баг.

Ну вообще да, похоже, т.к. в сообщении об ошибке про 'const volatile int&' говорится, что это non-const reference.

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

отбросятся cv-квалификаторы

Да, точно. Этот момент упустил из виду. Не в курсе, почему для volatile такое исключение из правил?

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

Не в курсе, почему для volatile такое исключение из правил?

Нет.

Наверное, так же, как Страуструп решил, что модифицировать временные объекты быссмысленно и запретил биндить не-const ссылки к ним, так и разрешать volatile-доступ ко временным объектам посчитали излишеством.

sostupid ()
Ответ на: комментарий от xaizek
template <typename T, typename ...A>
using trait_t = void;

template<typename... As>
using trait_type = trait_t<int, As...>;

ok, тип Т то надо для начала указать

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

volatile-доступ ко временным объектам

Точно, это оно. Спасибо, закэшировал

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

Вопрос в том, почему здесь надо, а для классов нет? Выглядит как будто компилятор не может отложить подстановку аргументов в trait_t до момента инстанциирования trait_type, как это делается для шаблонных классов.

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

гцц умнее всех, хотя так быть не должно

anonymous ()
#ifdef USING_VOLATILE
	#define VOLATILE volatile
#else
	#define VOLATILE
#endif

void f(const int&) {}
void f(const volatile int&) {}



При USING_VOLATILE будет определение функций с одинаковыми сигнатурами.

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

Нет, сигнатуры здесь «хардкодом», в них же макрос не используется. Здесь вот в этом дело:

Otherwise, if the reference is an lvalue reference to a type that is not const-qualified or is volatile-qualified, the program is ill-formed

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

Всем спасибо за ответы

Жаль (или нет?), я не могу такого написать в своей теме...

sostupid ()
Ответ на: комментарий от Deleted

Мой поинт в том, что компиляция прервется с ошибкой, в строке определения второй f(...), если будет установлен USING_VOLATILE.

p.s. Я не утверждаю, что это единственная проблема в представленном коде, а лишь обратил ваше внимание на проблемный код в самом начале.

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