У меня есть шаблон класса (OutgoingPacket) с двумя различными функциями:
void _prepare() {
assert(false); // this should have been specialized and the native function never called.
}
template <typename Args, typename ... RemArgs>
void _prepare(Args&& args, RemArgs&& ... remArgs) {
assert(false); // this should have been specialized and the native function never called.
}
Затем я определяю некоторые из этих двух специализаций вне определения класса:
// no args
template <> void OutgoingPacket<PacketServerAck> ::_prepare();
template <> void OutgoingPacket<PacketSup> ::_prepare();
template <> void OutgoingPacket<PacketWelcome> ::_prepare();
template <> void OutgoingPacket<PacketServerPing>::_prepare();
// with args
template <> template <> void OutgoingPacket<PacketGTFO>::_prepare<std::string>(std::string&& message);
template <> template <> void OutgoingPacket<PacketPlayer>::_prepare<std::shared_ptr<User>>(std::shared_ptr<User>&& user);
Вызовы функций для подготовки без аргументов работают должным образом, но вызовы перегрузки с аргументами вызывают базовый шаблон; они вызывают утверждение.
Почему это происходит?
Обновление: я только что попытался изменить определения специализации, чтобы включить ссылку с тем же результатом:
template <> template <> void OutgoingPacket<PacketGTFO>::_prepare<std::string&&>(std::string&& message);
template <> template <> void OutgoingPacket<PacketPlayer>::_prepare<std::shared_ptr<User>&&>(std::shared_ptr<User>&& user);
Кстати, причина, по которой я так поступаю, заключается в том, что я не чувствовал, что базовый класс OutgoingPacket должен быть завален всеми этими различными версиями функции prepare. И я не чувствовал, что подклассы были бы уместными, потому что различия между различными исходящими пакетами были бы очень маленькими (~ 4 строки).
По сути, объект OutgoingPacket создается с произвольными аргументами, которые затем направляются в функцию prepare:
template<typename ... Args>
OutgoingPacket(Args&&... args) {
_prepare(std::forward<Args>(args)...);
}
Если это плохая практика, могу ли я получить совет по дизайну?
Причина, скорее всего, в том, что вы вызываете свои функции с аргументами, которые не rvalues.
Обратите внимание, что параметры функции вашего основного шаблона переменной связаны как с rvalue, так и с lvalue: другими словами, они универсальные ссылки (нестандартный термин Скотта Мейерса), а параметры функции в вашем специализированном шаблоне связываются только к значениям.
// The parameters are universal references
template <typename Args, typename ... RemArgs>
void _prepare(Args&& args, RemArgs&& ... remArgs)
// The parameters are NOT universal references
template <> template <>
void OutgoingPacket<PacketGTFO>::_prepare<std::string>(std::string&& message);
Увидеть Эта статья а также эта презентация для объяснения того, как работают универсальные ссылки. Сложность в том, что &&
Суффикс не всегда означает ссылку на значение rvalue: например, в случае вашего основного шаблона это не так, но в случае вашего специализированного шаблона это так.
Таким образом, если входными аргументами вашей функции являются lvalues (например, переменные с именами, а не возвращаемым значением вызова функции), они будут привязаны к вашему первичному шаблону, а не к вашему специализированному шаблону, из-за обычных правил разрешения перегрузки.
Других решений пока нет …