如何在C ++ 11中为SFINAE和静态断言定义约束

时间:2019-04-03 15:06:59

标签: c++ c++11 templates sfinae static-assert

我正在尝试使用模板进行序列化/反序列化,现在可以正常工作了。 显然,在实现它时,我遇到了数百个编译器错误日志的许多麻烦。 在进一步扩展我的库之前,我想使用SFINAE(到目前为止我很少使用)和static_asserts对其进行保护。

不要再弄乱我的lib了,我正在沙盒中训练:

http://coliru.stacked-crooked.com/a/9eb4eaefaac90fc0

我想定义一些谓词:

  • is_a_base检查对象是从Base派生的。
  • is_insertable和is_extractable可以检查是否已为此类型定义了operator >>和operator <<。

我希望能够将这些谓词用于SFINAE专业化和static_assert。

#include <sstream>
#include <iostream>

//forward declaration
class Base;

//"Template typedef" to check predicates
template <typename T>
using is_a_Base = typename std::enable_if<std::is_base_of<Base, T>::value, void>::type;

template <typename T, typename is = std::istream>
using is_extractable = decltype (is{} >> T{});

template <typename T, typename os = std::ostream>
using is_insertable = decltype (os{} << T{});

//Test classes
class Base{
    public:
        std::string getStr(){ return "Base.getStr()";}
};
class Derived: public Base {};

class Other{};

//A template class with its specializations with SFINAE
template <typename T, typename Enable = void>
class C{
    public:
        static void f(T& o){
            std::cout << "f<T> default !" << std::endl;
    }
};

template<typename T>
class C<T, is_a_Base<T>>
{
    public:
        static void f (T& o)
        {
            std::cout << "f<is_a_A>() ! " << o.getStr() << std::endl;
        }
};

template<typename T>
class C<T, is_insertable<T> >
{
    public:
        static void f (T& o)
        {
            std::cout << "f<is_insertable() ! " << o << std::endl;
        }
};


template<typename T>
std::string g(T& ref)
{
    //static_assert(is_a_Base<T>, "T is not a Base"); //can't figure out the syntax here
    return ref.getStr();
}



int main(){
    Base a;
    Derived b;
    int myint = 1;
    std::string str="toto";

    Other oops;


    C<Base>::f(a);
    C<Derived>::f(b);
    C<int>::f(myint);      //Not calling is_insertable ??
    C<std::string>::f(str); //Not calling is_insertable ??
    C<Other>::f(oops);

    std::cout << "g:" << g(a) << std::endl;
    //std::cout << "g:" << g(oops) << std::endl; //should be blasted by the static assert
}

结果:

f<is_a_A>() ! Base.getStr()
f<is_a_A>() ! Base.getStr()
f<T> default !
f<T> default !
f<T> default !
g:Base.getStr()

到目前为止,is_a_base正在为SFINAE工作。 但是,is_insertable对int和string变量不起作用吗? 同样,我也想不出如何正确地将is_a_base谓词重用到我的assert语句中。

(由于我受交叉编译器的约束,因此不支持C ++ 11以后的版本,所以我不能使用C ++ 14及其以后的版本。)

2 个答案:

答案 0 :(得分:3)

您正在使用SFINAE通过默认参数C选择void的特殊化。这意味着该专业化的签名必须正确替代,但是还要产生void来选择要选择的专业化。就目前而言,您的流特征:

template <typename T, typename is = std::istream>
using is_extractable = decltype (is{} >> T{});

template <typename T, typename os = std::ostream>
using is_insertable = decltype (os{} << T{});

...实际上会从operator >>(分别为<<)产生返回类型,通常是is &(分别为os &)。您的命名也有些混乱,因为is_...表示一个布尔值,而不是布尔值。因此,我建议采取以下措施:

模板

using enable_if_base = typename std::enable_if<std::is_base_of<Base, T>::value>::type;
//                                              `void` is the default already ^

template <typename T, typename is = std::istream>
using enable_if_extractable = decltype (std::declval<is &>() >> std::declval<T &>(), void());
//                                                                                 ^^^^^^^^

template <typename T, typename os = std::ostream>
using enable_if_insertable = decltype (std::declval<os &>() << std::declval<T const &>(), void());
//                                                                                      ^^^^^^^^

请注意,我还用适当资格的T{}替换了std::declval,因为您不能假设相关类型实际上是默认可构造的,实际上是std::istream和{{1 }}不是。

答案 1 :(得分:2)

两个问题:

  1. std::ostreamstd::istream没有默认的构造函数,因此以您做的方式创建它们会导致替换失败,因此将主模板用作后备。使用declval<T&>可以避免使用默认构造函数创建临时文件。

  2. 主模板中的第二个模板参数必须为void才能被选择。有了它,它返回[i/o]s{} << T{}(原文如此)的结果类型,将是std::ostream&,另一个返回std::istream&。您可以通过将第二个参数包装在std::void_t中来解决此问题。

#1

template <typename T, typename is = std::istream>
using is_extractable = decltype (std::declval<is&>() >> T{});

template <typename T, typename os = std::ostream>
using is_insertable = decltype (std::declval<os&>() << T{});

#2

template<typename T>
class C<T, std::void_t<is_a_Base<T>>>

template<typename T>
class C<T, std::void_t<is_insertable<T>>>