Благодаря 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:
Спасибо!