Reputation: 1766
I'm trying to implement a simple serializer class, which has a Serialize function that dispatches the actual serialization to different overloaded function templates, selected at compile-time using expression-SFINAE with decltype:
#ifndef SERIALIZER_H
#define SERIALIZER_H
#include <string>
#include <utility>
/**** base/primary template for partial/explicit specialization on serializable classes ****/
template <typename T>
struct SerializeHelper;
/**** base abstract serialize class ****/
class Serializer
{
public:
// main serializer member function template
template <typename T>
void Serialize(const std::string &name, const T &value) const;
private:
virtual void Prepare(const std::string &name) const = 0;
virtual void Finalize(const std::string &name) const = 0;
// natively supported types
template <typename T>
auto Serialize(const T &value) const -> decltype(SerializeNative(value), std::declval<void>());
// use static function in class template specialized for type (partial or explicit specialization)
template <typename T>
auto Serialize(const T &value) const -> decltype(SerializeHelper<T>::Serialize(*this, value), std::declval<void>());
// use serializable type interface
template <typename T>
auto Serialize(const T &value) const -> decltype(value.Serialize(*this), std::declval<void>());
private:
// virtual functions for natively supported types
virtual void SerializeNative(int value) const = 0;
virtual void SerializeNative(float value) const = 0;
virtual void SerializeNative(double value) const = 0;
virtual void SerializeNative(const std::string &value) const = 0;
protected:
Serializer() = default;
};
template <typename T>
void Serializer::Serialize(const std::string &name, const T &value) const
{
Prepare(name);
Serialize(value);
Finalize(name);
}
// natively supported types
template <typename T>
auto Serializer::Serialize(const T &value) const -> decltype(SerializeNative(value), std::declval<void>()) // COMPILER ERROR
{
SerializeNative(value);
}
// use serialize function specialized for type
template <typename T>
auto Serializer::Serialize(const T &value) const -> decltype(SerializeHelper<T>::Serialize(*this, value), std::declval<void>())
{
SerializeHelper<T>::Serialize(*this, value);
}
// use serializable type interface
template <typename T>
auto Serializer::Serialize(const T &value) const -> decltype(value.Serialize(*this), std::declval<void>())
{
value.Serialize(*this);
}
#endif // SERIALIZER_H
Problem is, this code doesn't compile, since the compiler complains that the out of class definition of Serialize for native types doesn't have a corresponding declaration inside the class:
In file included from main.cpp:1:
serializer.hpp:53:6: error: no declaration matches 'decltype ((((const Serializer*)this)->Serializer::SerializeNative(value), declval<void>())) Serializer::Serialize(const T&) const'
auto Serializer::Serialize(const T &value) const -> decltype(SerializeNative(value), std::declval<void>())
If I put the inline definition inside the class it compiles fine. It happens with GCC and VC++.
EDIT
the code works fine if I declare the SerializeNative member functions before the member function template declaration, seems like since the call to the SerializeNative function is inside the Serialize function header (in decltype) it needs to see the declaration.
Upvotes: 2
Views: 304
Reputation: 48467
The reason the compiler cannot match the definition with the declaration is the following:
A name used in the definition of a class
X
outside of a complete-class context ([class.mem]) ofX
shall be declared in one of the following ways:
- before its use in class X or be a member of a base class of X ([class.member.lookup]), or
- [...]
where [class.mem]/p6:
A complete-class context of a class is a
- function body ([dcl.fct.def.general]),
- default argument,
- noexcept-specifier, or
- default member initializer
within the member-specification of the class.
That is, at the point of declaration:
template <typename T>
auto Serialize(const T &value) const -> decltype(SerializeNative(value), std::declval<void>());
the name SerializeNative
is not found by the name lookup, because SerializeNative
is declared after its use, while it is found in the definition, causing the mismatch.
In order to use SerializeNative
in the expression SFINAE, you need to declare the private virtual functions before using their name in the return type of Serialize
.
The error for SerializeNative(value)
is not immediately reported, because that function could potentially be found in argument-dependent lookup, as soon as the type for value
is known.
Upvotes: 2