Как определить ограничения для SFINAE и статического утверждения в C ++ 11 - PullRequest
2 голосов
/ 03 апреля 2019

Я экспериментирую с сериализацией / десериализацией с шаблоном, и теперь что-то работает.Очевидно, что при его реализации я столкнулся со многими проблемами с сотнями журналов ошибок компилятора.Прежде чем расширять свою библиотеку, я хотел бы немного ее обезопасить, используя SFINAE (которую я до сих пор использовал редко) и static_asserts.

Чтобы больше не связываться с моей библиотекой, я тренируюсь в песочнице:

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

Я хочу определить несколько предикатов:

  • is_a_base для проверки объекта, производного от Base.
  • is_insertable & is_extractable, чтобы проверить, были ли операторы >> и оператор << былиопределены для этого типа.</li>

Я хочу использовать эти предикаты как для специализации 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 ]

3 голосов
/ 03 апреля 2019

Вы используете 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 и std::ostream не являются.

2 голосов
/ 03 апреля 2019

Две проблемы:

  1. std::ostream и std::istream не имеют конструкторов по умолчанию, поэтому их создание так, как вы это сделали, вызвало ошибку замещения, поэтому основной шаблон использовался какотступать.Используйте declval<T&>, чтобы избежать создания временного конструктора по умолчанию.

  2. Второй аргумент шаблона в первичном шаблоне должен быть void, чтобы его можно было выбрать.Как он у вас есть, он возвращает тип результата [i/o]s{} << T{} (sic), который будет 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>>>
...