【问题标题】:How to use SFINAE to create 2 different implementations of the same method如何使用 SFINAE 创建相同方法的 2 个不同实现
【发布时间】:2014-11-04 10:20:02
【问题描述】:

我已阅读有关 SFINAE 的一些文章,但找不到适合我的情况的解决方案。这是我想做的:

#include <type_traits>

struct CByteArray {};
struct HLVariant {
    HLVariant() {}
    HLVariant(const HLVariant&) {}
    HLVariant(const CByteArray&) {}

    };

template <typename T>
struct Serializer
{
    static inline typename std::enable_if<std::is_pod<T>::value, CByteArray>::type serialize(const T& value)
    {
        static_assert(std::is_pod<T>::value, "Not a POD type");
        return CByteArray();
    }

    static inline typename std::enable_if<!std::is_pod<T>::value, CByteArray>::type serialize(const T& value)
    {
        return Serializer<HLVariant>::serialize(HLVariant(value));
    }
};

template <>
struct Serializer<HLVariant>
{
    static inline CByteArray serialize(const HLVariant& value)
    {
        return CByteArray();
    }
};

int main()
{
    int i = 0;
    Serializer<int>::serialize(i);
    Serializer<CByteArray>::serialize(CByteArray());
    Serializer<HLVariant>::serialize(HLVariant());

    return 0;
}

当然,我收到的是error C2039: 'type' : is not a member of 'std::enable_if&lt;false,CByteArray&gt;'

如何实现我想要的?

另外,是否有可能以某种方式重新组织Serializer,以便可以隐式推导出模板参数-Serializer::serialize(i); 而不是Serializer&lt;int&gt;::serialize(i);

【问题讨论】:

  • @Nawaz 投票取消删除您的答案,因为它提出了更好的问题替代方案。

标签: c++ templates c++11 sfinae


【解决方案1】:

为了使用std::enable_if&lt;condition&gt;,您必须在模板中超过条件。一种选择是将您的函数声明为具有默认参数的模板

template <typename T>
struct Serializer
{
    template<bool pod = std::is_pod<T>::value>  // template over condition
    static typename std::enable_if<pod, CByteArray>::type
    serialize(const T& value)
    { return CByteArray(); }

    template<bool pod = std::is_pod<T>::value>
    static typename std::enable_if<!pod, CByteArray>::type 
    serialize(const T& value)
    { return Serializer<HLVariant>::serialize(HLVariant(value)); }
};

template<>
struct Serializer<HLVariant>
{
    static CByteArray serialize(const HLVariant&);
};

或者,您可以直接在类模板范围内应用 SFINAE:

template<typename T, typename = void> struct Serializer;

template<>
struct Serializer<HLVariant>
{
    static CByteArray serialize(const HLVariant&)
    { return CByteArray(); }
};

template<typename T>
struct Serializer<T,typename std::enable_if<is_pod<T>::type>
{
    static CByteArray serialize(const T&)
    { return CByteArray(); }
};

template<typename T>
struct Serializer<T,typename std::enable_if<!is_pod<T>::type>
{
    static CByteArray serialize(const T&value)
    { return Serializer<HLVariant>::serialize(HLVariant(value));
};

或者你可以去掉类 Serializer 并直接通过模板函数声明它:

inline CByteArray
serialize(const HLVariant&)
{ return CByteArray(); }

template<typename T>
inline typename enable_if<std::is_pod<T>::value, CByteArray>::type
serialize(const T&)
{ return CByteArray(); }

template<typename T>
inline typename enable_if<!std::is_pod<T>::value, CByteArray>::type
serialize(const T&value)
{ return serialize(HLVariant(value)); }

顺便说一句,C++14 定义了非常有用的别名

template<bool C, typename T>
using enable_if_t = typename enable_if<C,T>::type;

当然,您也可以这样做。这样就避免了繁琐的typename::type

【讨论】:

  • 你的最后一个建议是我开始的。它可以工作,但是随着我不断添加“专业化”(因为缺少更好的词;这些当然不是 C++ 术语中的专业化),很快变得太复杂了。
  • 恐怕,为通用输入类型编写通用功能并针对不同输入类型行为进行不同实现会很复杂......
  • 如果专业化以合理的方式工作,则不会!看起来我们真的需要某种 static if 来解决这个问题。
  • ??我不明白。这里的“专业化”是什么意思?如果它们是静态的(正如您的 cmets 所建议的那样),那么它们就是 C++ 意义上的特化。
【解决方案2】:

SFINAE 是“替代失败不是错误”的首字母缩写词。根据定义,这意味着它仅适用于在模板定义中用模板实参代替参数时。您的 serialize 函数是类模板的成员函数,它们本身不是函数模板。直接的答案是将函数转换为函数模板(Live code):

template <typename> struct Serializer;

template <>
struct Serializer<HLVariant>
{
    static CByteArray serialize(const HLVariant& /* value */)
    {
        return CByteArray();
    }
};

template <typename T>
struct Serializer
{
    template <typename U = T>
    static typename std::enable_if<std::is_pod<U>::value, CByteArray>::type
    serialize(const U& /* value*/)
    {
        static_assert(std::is_pod<U>::value, "Not a POD type");
        return CByteArray();
    }

    template <typename U = T>
    static typename std::enable_if<!std::is_pod<U>::value, CByteArray>::type
    serialize(const U& value)
    {
        return Serializer<HLVariant>::serialize(HLVariant(value));
    }
};

我已经删除了多余的inlines,因为类主体中定义的所有函数都是隐式内联的,并且我重新定位了Serializer&lt;HLVariant&gt; 特化以确保它在被引用之前被正确声明。有一个只有静态成员函数的类有点傻;您可以更合理地将其实现为一组重载函数 (Live code):

inline CByteArray serialize(const HLVariant& /* value */)
{
    return CByteArray();
}

template <typename T>
inline typename std::enable_if<std::is_pod<T>::value, CByteArray>::type
serialize(const T& /* value*/)
{
    static_assert(std::is_pod<T>::value, "Not a POD type");
    return CByteArray();
}

template <typename T>
inline typename std::enable_if<!std::is_pod<T>::value, CByteArray>::type
serialize(const T& value)
{
    return serialize(HLVariant(value));
}

int main()
{
    int i = 0;
    serialize(i);
    serialize(CByteArray());
    serialize(HLVariant());
}

鉴于 SFINAE 使用妨碍代码可读性,我更愿意在这种情况下使用标签调度。不要使用 SFINAE 管理两个函数的重载解决方案,而是使用第三个函数调用 POD 或非 POD 的适当实现 (Yet more live code):

inline CByteArray serialize(const HLVariant& /* value */)
{
    return CByteArray();
}

template <typename T>
inline CByteArray serialize(std::true_type, const T& /* value*/)
{
    static_assert(std::is_pod<T>::value, "Not a POD type");
    return CByteArray();
}

template <typename T>
inline CByteArray serialize(std::false_type, const T& value)
{
    return serialize(HLVariant(value));
}

template <typename T>
inline CByteArray serialize(const T& value)
{
    return serialize(std::is_pod<T>{}, value);
}

SFINAE 功能强大,但也有足够的危险性,可以安全地锁起来解决您可以使用更简单的工具解决的问题。

【讨论】:

  • 很酷的把戏,第一个。现在我明白我的错误了。我最初是从免费模板函数而不是类模板开始的,但是 MSVC 中有一个错误使模板函数专业化在我的情况下无法使用:stackoverflow.com/questions/26716609/…
  • SFINAE 足够危险,可以安全地锁起来 它有什么危险?以及如何通过标签调度避免这些危险?我同意 SFINAE 会妨碍代码的可读性,但标签调度并没有好多少,实际上它很危险,因为不能保证不会使用错误的第二个参数调用 serialize(std::false_type, const T&amp; value)(因为它不是 private 或 @987654333 @)
猜你喜欢
  • 1970-01-01
  • 2013-07-16
  • 2022-01-21
  • 2015-03-23
  • 1970-01-01
  • 1970-01-01
  • 2015-04-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多