Благодаря decltype
как возвращаемому типу, С++ 11 чрезвычайно упростил внедрение декораторов. Например, рассмотрим этот класс:
struct base
{
void fun(unsigned) {}
};
Я хочу украсить его дополнительными функциями, и, поскольку я буду делать это несколько раз с различными видами украшений, я сначала представляю класс decorator
, который просто перенаправляет все на base
. В реальном коде это делается с помощью std::shared_ptr
, чтобы я мог удалить декорации и восстановить "голый" объект, и все шаблоны.
#include <utility> // std::forward
struct decorator
{
base b;
template <typename... Args>
auto
fun(Args&&... args)
-> decltype(b.fun(std::forward<Args>(args)...))
{
return b.fun(std::forward<Args>(args)...);
}
};
Отличная переадресация и decltype
просто замечательные. В реальном коде я фактически использую макрос, которому просто нужно имя функции, все остальное является шаблоном.
И затем я могу ввести класс derived
, который добавляет функции моему объекту (derived
является неправильным, согласованным, но он помогает понять, что derived
есть вид base
, хотя и не через наследование).
struct foo_t {};
struct derived : decorator
{
using decorator::fun; // I want "native" fun, and decorated fun.
void fun(const foo_t&) {}
};
int main()
{
derived d;
d.fun(foo_t{});
}
Затем появился С++ 14 с выводом типа возвращаемого типа, который позволяет писать вещи проще: удалите часть decltype
функции пересылки:
struct decorator
{
base b;
template <typename... Args>
auto
fun(Args&&... args)
{
return b.fun(std::forward<Args>(args)...);
}
};
И потом он ломается. Да, по крайней мере, согласно GCC и Clang, это:
template <typename... Args>
auto
fun(Args&&... args)
-> decltype(b.fun(std::forward<Args>(args)...))
{
return b.fun(std::forward<Args>(args)...);
}
};
не эквивалентен этому (и проблема не auto
vs. decltype(auto)
):
template <typename... Args>
auto
fun(Args&&... args)
{
return b.fun(std::forward<Args>(args)...);
}
};
Разрешение перегрузки кажется совершенно другим, и оно заканчивается следующим образом:
clang++-mp-3.5 -std=c++1y main.cc
main.cc:19:18: error: no viable conversion from 'foo_t' to 'unsigned int'
return b.fun(std::forward<Args>(args)...);
^~~~~~~~~~~~~~~~~~~~~~~~
main.cc:32:5: note: in instantiation of function template specialization
'decorator::fun<foo_t>' requested here
d.fun(foo_t{});
^
main.cc:7:20: note: passing argument to parameter here
void fun(unsigned) {}
^
Я понимаю неудачу: мой вызов (d.fun(foo_t{})
) отлично не совпадает с сигнатурой derived::fun
, которая принимает const foo_t&
, поэтому очень нетерпеливый decorator::fun
запускается (мы знаем, как Args&&...
крайне не терпит привязки ко всему, что не идеально подходит). Поэтому он пересылает это значение в base::fun
, которое не может иметь дело с foo_t
.
Если я изменю derived::fun
, чтобы взять foo_t
вместо const foo_t&
, то он работает так, как ожидалось, что показывает, что в действительности проблема заключается в том, что существует конкуренция между derived::fun
и decorator::fun
.
Однако почему черт делает это с возвратом типа вывода? И точнее, почему это поведение было выбрано комитетом?
Чтобы сделать вещи проще, на Coliru:
Спасибо!