Форум » C/C++ для начинающих (C/C++ for beginners) » Неоднозначность синтаксиса в C++. Как круглые скобки вокруг аргумента влияют на выбор шаблона. » Ответить

Неоднозначность синтаксиса в C++. Как круглые скобки вокруг аргумента влияют на выбор шаблона.

Сыроежка: В C++ есть такие "темные места", которые интуитивно не ясны, и такие возможности языка следует просто знать. Одно из таких "темных мест" связано с аргументами шаблонов. Имеется неоднозначность в интерпретации конструкций вида T(), где T - это некоторый тип, при использовании их в качестве аргументов шаблона. С одной стороны, такая конструкция может рассматриваться как задание типа (type-id), а, с другой стороны, как задание выражение. C++ независимо от контекста использования аргумента шаблона всегда рассматривает такую конструкцию как задание типа (type-id). Согласно параграфу №2 раздела 17.3 Template arguments [quote]2 In a template-argument, an ambiguity between a type-id and an expression is resolved to a type-id, regardless of the form of the corresponding template-parameter.[/quote] Например, в следующей демонстрационной программе будет вызвана функция с шаблонным параметром типа [pre2] #include <iostream> template <typename> void f() { std::cout << "The function with a type template parameter is called.\n"; } template <int> void f() { std::cout << "The function with a non-type template parameter is called.\n"; } int main() { f<int()>(); } [/pre2] Вывод программы на консоль будет [pre2] The function with a type template parameter is called. [/pre2] А как в таком случае, используя конструкцию вида T(), все же вызвать шаблонную функцию, которая имеет шаблонный параметр, не являющийся типом, то есть чтобы использовать эту конструкцию как выражение? Для этого достаточно заключить эту конструкцию в круглые скобки, и тогда однозначно получится выражение. Например, [pre2] #include <iostream> template <typename> void f() { std::cout << "The function with a type template parameter is called.\n"; } template <int> void f() { std::cout << "The function with a non-type template parameter is called.\n"; } int main() { f<int()>(); f<( int() )>(); } [/pre2] Вывод программы на консоль будет [pre2] The function with a type template parameter is called. The function with a non-type template parameter is called. [/pre2] Обратите внимание на то, что теперь, согласно стандарту C++ 17, вторая шаблонная функция также может быть определена, используя спецификатор auto в качестве объявления шаблонного параметра. Ниже приведена соответствующая демонстрационная программа. [pre2] #include <iostream> template <typename> void f() { std::cout << "The function with a type template parameter is called.\n"; } template <auto> void f() { std::cout << "The function with a non-type template parameter is called.\n"; } int main() { f<int()>(); f<( int() )>(); } [/pre2] Вывод этой демонстрационной программы аналогичен выводу предыдущей демонстрационной программы [pre2] The function with a type template parameter is called. The function with a non-type template parameter is called.[/pre2]

Ответов - 1

Сыроежка: Вот еще один пример на неоднозначность синтаксических конструкция в C++. Рассмотрим следующую программу. [pre2] #include <iostream> #include <memory> int main() { std::shared_ptr<int> p( new int( 42 ) ); std::cout << "#1: " << p.use_count() << '\n'; { std::cout << "#2: " << p.use_count() << '\n'; std::shared_ptr<int>( p ); std::cout << "#3: " << p.use_count() << '\n'; } std::cout << "#4: " << p.use_count() << '\n'; } [/pre2] Спрашивается, какие значения use_count() программа выведет на консоль. Сложность ответа на вопрос может вызывать наличие конструкции [pre2] std::shared_ptr<int>( p );[/pre2] во внутреннем блоке кода функции main. То есть как эта конструкция влияет на вывод? Согласно стандарту C++ (8.5.1.3 Explicit type conversion (functional notation)) 1 A simple-type-specifier (10.1.7.2) or typename-specifier (17.7) followed by a parenthesized optional expressionlist or by a braced-init-list (the initializer) constructs a value of the specified type given the initializer.... То есть согласно этой цитате данное предложение [pre2] std::shared_ptr<int>( p );[/pre2] представляет собой предложение выражения, содержащее выражение явного (функционального) приведения типов. Это предложение никак не может повлиять на вывод значения p.use_count() в последующих предложениях. Но, с другой стороны, данное предложение можно рассматривать как объявление локальной переменной p, так как деклараторы можно заключать в круглые скобки. Действительно, например, следующее предложение [pre2]int ( x );[/pre2] является корректным объявлением переменной x, имеющей тип int. Таким образом предложение [pre2] std::shared_ptr<int>( p );[/pre2] является объявлением локальной переменной p, имеющей тип std::shared_ptr<int>. Для наглядности можно спецификатор типа и декларатор разделить дополнительным пробелом [pre2] std::shared_ptr<int> ( p );[/pre2] Как в C++ разрешается такая неоднозначноть? Согласно разделу 9.8 Ambiguity resolution 1 There is an ambiguity in the grammar involving expression-statements and declarations: An expression-statement with a function-style explicit type conversion (8.5.1.3) as its leftmost subexpression can be indistinguishable from a declaration where the first declarator starts with a (. In those cases the statement is a declaration Итак, стандарт разрешает эту неоднозначность в пользу объявления переменной. Следовательно в программе во внутреннем блоке объявляется новая локальная переменная p, которая скрывает одноименную переменную p, объявленную во внешнем блоке, используя конструктор по умолчанию [pre2]constexpr shared_ptr() noexcept;[/pre2] Согласно описанию этого конутрктора 2 Effects: Constructs an empty shared_ptr object. 3 Postconditions: use_count() == 0 && get() == nullptr Учитывая все сказанное, вывод программы на консоль будет выглядеть следующим образом: [pre2] #1: 1 #2: 1 #3: 0 #4: 1[/pre2] То есть в третьем предложении вывода в поток будет выведено на консоль значение use_count, равное 0 для нового только что созданного локального объекта p. А как это предложение с объявлением превратить в предложение выражения? Достаточно просто заключить тело предложения в круглые скобки, как показано в программе ниже. [pre2] #include <iostream> #include <memory> int main() { std::shared_ptr<int> p( new int( 42 ) ); std::cout << "#1: " << p.use_count() << '\n'; { std::cout << "#2: " << p.use_count() << '\n'; ( std::shared_ptr<int>( p ) ); std::cout << "#3: " << p.use_count() << '\n'; } std::cout << "#4: " << p.use_count() << '\n'; } [/pre2] И тогда вывод на консоль уже будет выглядеть следующим образом: [pre2] #1: 1 #2: 1 #3: 1 #4: 1[/pre2] Альтернативный подход, если не использовать круглые скобки, состоит в том, чтобы записать выражение явного функционального приведения типа с использованием фигурных скобок. Ниже показано, как будет выглядеть соответствующее предложение. [pre2] #include <iostream> #include <memory> int main() { std::shared_ptr<int> p( new int( 42 ) ); std::cout << "#1: " << p.use_count() << '\n'; { std::cout << "#2: " << p.use_count() << '\n'; std::shared_ptr<int>{ p }; std::cout << "#3: " << p.use_count() << '\n'; } std::cout << "#4: " << p.use_count() << '\n'; } [/pre2] Вывод на консоль будет таким же, как показано выше [pre2] #1: 1 #2: 1 #3: 1 #4: 1[/pre2] Это сообщение я создал на основе вопроса на Stackoverflow Understanding C++ std::shared_ptr when used with temporary objects и моего ответа на него.



полная версия страницы