std::forward, зачем typename remove_reference<_Ty>::type?
От: Engler Беларусь  
Дата: 10.07.14 18:10
Оценка:
Всем привет,

Перечитал и пересмотрел кучу материала по теме RValue references
  Скрытый текст
http://www.rsdn.ru/forum/cpp/3731204.1
Автор: Masterkent
Дата: 11.03.10

http://thbecker.net/articles/rvalue_references/section_01.html
http://isocpp.org/blog/2012/11/universal-references-in-c11-scott-meyers
http://stackoverflow.com/questions/7779900/why-is-template-argument-deduction-disabled-with-stdforward
http://channel9.msdn.com/Events/GoingNative/2013/An-Effective-Cpp11-14-Sampler

Тема вроде уже заезжанная, но вот не могу уложить себе один момент в голове.

  Скрытый текст
    // TEMPLATE FUNCTION forward
template<class _Ty> inline
    _Ty&& forward(typename remove_reference<_Ty>::type& _Arg) // Зачем реализовано именно так?
    {    // forward an lvalue
    return (static_cast<_Ty&&>(_Arg));
    }

template<class _Ty> inline
    _Ty&& forward(typename remove_reference<_Ty>::type&& _Arg) _NOEXCEPT // Зачем реализовано именно так?
    {    // forward anything
    static_assert(!is_lvalue_reference<_Ty>::value, "bad forward call");
    return (static_cast<_Ty&&>(_Arg));
    }

// Здесь со специализациями шаблона, понятно как это работает. Но вот непонятно почему это реализовано именно так?
    // TEMPLATE remove_reference
template<class _Ty>
    struct remove_reference
    {    // remove reference
    typedef _Ty type;
    };

template<class _Ty>
    struct remove_reference<_Ty&>
    {    // remove reference
    typedef _Ty type;
    };

template<class _Ty>
    struct remove_reference<_Ty&&>
    {    // remove rvalue reference
    typedef _Ty type;
    };

Казалось бы, почему бы не сделать так:
  Скрытый текст
    // TEMPLATE FUNCTION forward
template<class _Ty> inline
    _Ty&& forward(typename remove_reference<_Ty>::type _Ty& _Arg) // Почему не так?
    {    // forward an lvalue
    return (static_cast<_Ty&&>(_Arg));
    }

template<class _Ty> inline
    _Ty&& forward(typename remove_reference<_Ty>::type _Ty&& _Arg) _NOEXCEPT // Почему не так?
    {    // forward anything
    static_assert(!is_lvalue_reference<_Ty>::value, "bad forward call");
    return (static_cast<_Ty&&>(_Arg));
    }


А дальше начинается самое интересное.
При таком коде компилятор (от VS 2012 и g++ 4.8.2)
  Скрытый текст
ПРИМЕР1 :
template<typename T>
void Test(T&) {}

template<typename T>
void Test(T&&) {}

int main()
{
    X x1;
    Test(x1);
}


Это от MS compiler
error C2668: 'Test' : ambiguous call to overloaded function
1> f:\projects\c++\stl_tests\main.cpp(94): could be 'void Test<X&>(T)'
1> with
1> [
1> T=X &
1> ]
1> f:\projects\c++\stl_tests\main.cpp(91): or 'void Test<X>(T &)'
1> with
1> [
1> T=X
1> ]
1> while trying to match the argument list '(X)'



Да, я знаком с "reference collapsing rules". Т.е почему так происходит я понимаю, но я не совсем понимаю, почему не выбирается явная реализация:

  Скрытый текст
template<typename T>
void Test(T&) {}


Почему так должно быть? Ну например, при перегрузке функций с явными типами все работает логично ( т.е выбирвается TestInt(int&) , т.к параметр x1 lvalue ) :

  Скрытый текст
void TestInt(int &) {}
void TestInt(int &&) {}

int main()
{

    int x1;
    TestInt(x1);
}


Небольшой поиск наталкивает:
На следующую тему: Overload ambiguity when passing R-value to function that takes L-value
Оттуда есть ссылка сюда: 1164. Partial ordering of f(T&amp;) and f(T&amp;&amp;)

В драфтовой версии С++ (n3793) в 14.8.2.4/9 Изменения внесли.

А, вот теперь вопросы:

1. Поведение компилятовов на код из "ПРИМЕР1", получается не соответствуют стандарту?
2. Именно по этому приходится извращаться с
typename remove_reference<_Ty>::type
в параметрах?
Re: std::forward, зачем typename remove_reference<_Ty>::type?
От: Abyx Россия  
Дата: 10.07.14 18:21
Оценка: 1 (1)
Здравствуйте, Engler, Вы писали:

E>А дальше начинается самое интересное.

E>При таком коде компилятор (от VS 2012 и g++ 4.8.2)

почему Вы не проверяете на последних версиях компиляторов?
если не хотите ставить компилятор на свою машину — используйте онлайн компилятор, их много — https://isocpp.org/get-started
вот например CoLiRu — http://coliru.stacked-crooked.com/a/04602d97319d67f1
In Zen We Trust
Re[2]: std::forward, зачем typename remove_reference<_Ty>::type?
От: Engler Беларусь  
Дата: 10.07.14 18:37
Оценка:
A>почему Вы не проверяете на последних версиях компиляторов?
Хороший вопрос
A>если не хотите ставить компилятор на свою машину — используйте онлайн компилятор, их много — https://isocpp.org/get-started
A>вот например CoLiRu — http://coliru.stacked-crooked.com/a/04602d97319d67f1

Да, проверил на gcc 4.9.0, действительно работает как надо
Re: std::forward, зачем typename remove_reference<_Ty>::type?
От: Abyx Россия  
Дата: 10.07.14 20:20
Оценка: 6 (1) +1
Здравствуйте, Engler, Вы писали:

вообще начать надо с того, при типичном использовании forward, она всегда работает в lvalue, и вторая форма (c &&) не нужна.
template<typename T>
void test(T&& t) { use(forward<T>(t)); }


по этому достаточно
template <class T> T&& forward(T& t) noexcept { return static_cast<T&&>(t); }
In Zen We Trust
Re[2]: std::forward, зачем typename remove_reference<_Ty>::type?
От: Evgeny.Panasyuk Россия  
Дата: 10.07.14 22:06
Оценка:
Здравствуйте, Abyx, Вы писали:

A>вообще начать надо с того, при типичном использовании forward, она всегда работает в lvalue, и вторая форма (c &&) не нужна.

A>
A>template<typename T>
A>void test(T&& t) { use(forward<T>(t)); } 
A>


Помню когда изучал forward, наличие этой второй формы сбивало с толку. Всё стало на свои места, когда понял что по сути используется только один вариант.
Re: std::forward, зачем typename remove_reference<_Ty>::type?
От: uzhas Ниоткуда  
Дата: 11.07.14 06:53
Оценка: 12 (1)
Здравствуйте, Engler, Вы писали:

E>Перечитал и пересмотрел кучу материала по теме RValue references

...

E>Тема вроде уже заезжанная, но вот не могу уложить себе один момент в голове.


ответ по вашей же ссылке: http://thbecker.net/articles/rvalue_references/section_08.html

If you want to dig a little deeper for extra credit, ask yourself this question: why is the remove_reference in the definition of std::forward needed? The answer is, it is not really needed at all. If you use just S& instead of remove_reference<S>::type& in the defintion of std::forward, you can repeat the case distinction above to convince yourself that perfect forwarding still works just fine. However, it works fine only as long as we explicitly specify Arg as the template argument of std::forward. The purpose of the remove_reference in the definition of std::forward is to force us to do so.


если коротко то общие правила такие:
1) в std:::move не следует указывать тип: std::move(var), а не std::move<T>(var)
2) в std::forward необходимо указать тип: std::forward<T>(var), а не std::forward(var)

если вы отошли от этого правила, то высока вероятность, что вы делаете что-то не так
Re[2]: std::forward, зачем typename remove_reference<_Ty>::type?
От: Engler Беларусь  
Дата: 11.07.14 09:49
Оценка:
Здравствуйте, uzhas, Вы писали:

U>ответ по вашей же ссылке: http://thbecker.net/articles/rvalue_references/section_08.html

U>

U>If you want to dig a little deeper for extra credit, ask yourself this question: why is the remove_reference in the definition of std::forward needed? The answer is, it is not really needed at all. If you use just S& instead of remove_reference<S>::type& in the defintion of std::forward, you can repeat the case distinction above to convince yourself that perfect forwarding still works just fine. However, it works fine only as long as we explicitly specify Arg as the template argument of std::forward. The purpose of the remove_reference in the definition of std::forward is to force us to do so.


Я вчитывался в этот момент, ни один раз.Просто пробовал на старой версии gcc (4.8.1/2) тесты вели себя чуть-чуть по другому, поэтому и вгоняло в ступор.
Сейчас другой момент (gcc 4.9.0):

#include <iostream>
#include <utility>
class X{};

template<typename T>
void Test(T&) { std::cout<< 1;}

template<typename T>
void Test(T&&) {std::cout<< 2; }

template<typename T>
T&& myForward(T& arg)
{
    return static_cast<T&&>(arg);
}

template<typename T>
void Func(T&& arg)
{
   // Test(std::forward<T>(arg));
   // Test(myForward<T>(arg));     Работают экквивалентно. Выводит 12
   
   // 
   // В этом моменте вывод типов срабатывает немного по-другому. Почему?
   // 
   Test(myForward(arg));     // Выводит 22
}

int main()
{
    X x1;
// lvalue : Expected version: Test(T&) 
    Func(x1);

// rvalue : Expected version: Test(T&&) 
    Func(X());
}

Инетерсно с чем связано такое поведение?

U>2) в std::forward необходимо указать тип: std::forward<T>(var), а не std::forward(var)

U>если вы отошли от этого правила, то высока вероятность, что вы делаете что-то не так
Ну вот так-то и не даст сделать.
Re[2]: std::forward, зачем typename remove_reference<_Ty>::type?
От: Engler Беларусь  
Дата: 11.07.14 09:51
Оценка:
Здравствуйте, Abyx, Вы писали:

A>Здравствуйте, Engler, Вы писали:


A>вообще начать надо с того, при типичном использовании forward, она всегда работает в lvalue, и вторая форма (c &&) не нужна.

Почему ее тогда в библиотеке внесли (или оставили)? в С++ 14?
Re[3]: std::forward, зачем typename remove_reference<_Ty>::type?
От: Abyx Россия  
Дата: 11.07.14 13:26
Оценка: 14 (2)
Здравствуйте, Engler, Вы писали:

A>>вообще начать надо с того, при типичном использовании forward, она всегда работает в lvalue, и вторая форма (c &&) не нужна.

E>Почему ее тогда в библиотеке внесли (или оставили)? в С++ 14?

В таких случаях надо читать proposal — http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2009/n2951.html
А именно случаи "B" и "С", где в forward передается rvalue.
In Zen We Trust
Re[2]: std::forward, зачем typename remove_reference<_Ty>::type?
От: Evgeny.Panasyuk Россия  
Дата: 11.07.14 15:27
Оценка: 1 (1)
U>2) в std::forward необходимо указать тип: std::forward<T>(var), а не std::forward(var)

Кстати, offtopic, тип T для forward можно извлечь из var, что позволяет спрятать всё в один макрос FORWARD(var) (по-моему прочитал эту идею в комментариях к статье Майерса):
#include <type_traits>
#include <utility>

#define FORWARD(x) \
    std::forward \
    < \
        typename std::conditional \
        < \
            std::is_rvalue_reference<decltype(x)>::value, \
            typename std::remove_reference<decltype(x)>::type, \
            decltype(x) \
        >::type \
    >(x) \
/**/

/*******************************************/

#include <iostream>

struct Foo
{
    Foo() = default;
    Foo(const Foo &)
    {
        std::cout << "copy" << std::endl;
    }
    Foo(Foo &&)
    {
        std::cout << "move" << std::endl;
    }
};

template<typename T>
void test(T &&x)
{
    Foo{std::forward<T>(x)};
    Foo{FORWARD(x)};
    std::cout << std::endl;
}

int main()
{
    Foo x;
    test(x);
    test(std::move(x));
    test(Foo{});
}

Вывод:
copy
copy

move
move

move
move
Re[3]: std::forward, зачем typename remove_reference<_Ty>::type?
От: Evgeny.Panasyuk Россия  
Дата: 13.10.14 18:09
Оценка:
EP>Кстати, offtopic, тип T для forward можно извлечь из var, что позволяет спрятать всё в один макрос FORWARD(var) (по-моему прочитал эту идею в комментариях к статье Майерса):
EP>
EP>#include <type_traits>
EP>#include <utility>

EP>#define FORWARD(x) \
EP>    std::forward \
EP>    < \
EP>        typename std::conditional \
EP>        < \
EP>            std::is_rvalue_reference<decltype(x)>::value, \
EP>            typename std::remove_reference<decltype(x)>::type, \
EP>            decltype(x) \
        >>::type \
    >>(x) \
EP>/**/
EP>


Сейчас подумал, ведь с полиморфными лямбдами нам придётся использовать подобную штуку (может без макроса, может новый шаблон функции и т.п.).
Сразу же наткнулся на статью Маейраса (другую) на эту тему:
http://scottmeyers.blogspot.ru/2013/05/c14-lambdas-and-perfect-forwarding.html
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.