Luca
Luca

Reputation: 1766

out of class definition of function template using expression-SFINAE

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

Answers (1)

Piotr Skotnicki
Piotr Skotnicki

Reputation: 48467

The reason the compiler cannot match the definition with the declaration is the following:

[basic.lookup.unqual]/p7:

A name used in the definition of a class X outside of a complete-class context ([class.mem]) of X shall be declared in one of the following ways:

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

Related Questions