Ошибки бросания decltype в шаблонных методах, которые не генерируют код

Я пытаюсь создать своего рода класс-обертку, который перенаправляет все операторы в содержащийся в нем объект, чтобы попытаться заставить его «притворяться» содержащимся в нем объектом. Код я бы лайк написать выглядит примерно так (упрощенно):

template<typename T>
class Wrapper
{
private:
T val;
public:
Wrapper(T value) : val(value) {}
auto operator++() -> decltype(++this->val)
{
return ++this->val;
}
};

Это прекрасно работает с int, но если я попытаюсь пройти std::string в это я получаю ошибку cannot increment value of type 'std::basic_string<char>',

Я также попытался использовать declval здесь, но это только усугубляло ситуацию, так как он не только вызывал ошибки std::string, это также бросило их на int в этом случае из-за int не быть классом.

Теперь, в обычных ситуациях, эта функция вообще не будет генерироваться, потому что я ее не вызываю. Но по какой-то причине decltype все еще обрабатывается в этой функции, даже если он вообще не генерируется. (Если я удаляю decltype и меняю тип возвращаемого значения на void, я могу скомпилировать std::string без проблем.)

Итак, мой вопрос: есть ли способ обойти это? Может быть, какой-то сумасшедший трюк с использованием SFINAE? Или, возможно, это неправильное поведение для компилятора, так как функция не генерирует код?

РЕДАКТИРОВАТЬ: Решение, несколько измененное по сравнению с решением, предложенным BЈовић:

//Class, supports operator++, get its declared return type
template<typename R, bool IsObj = boost::is_class<R>::value, bool hasOp = boost::has_pre_increment<R>::value> struct OpRet
{
typedef decltype(++std::declval<R>()) Ret;
};
//Not a class, but supports operator++, return type is R (i.e., ++int returns int)
template<typename R> struct OpRet<R, false, true>
{
typedef R Ret;
};
//Doesn't support operator++, return type is void
template<typename R> struct OpRet<R, true, false>
{
typedef void Ret;
};
template<typename R> struct OpRet<R, false, false>
{
typedef void Ret;
};

template<typename T>
class Wrapper
{
private:
T val;
public:
Wrapper(T value) : val(value) {}
auto operator++() -> typename OpRet<T>::Ret
{
return ++val;
}
};

Это будет работать как с простыми типами, так и с типами классов, а также с типами классов, а также в ситуациях, когда возвращаемый тип оператора ++ не равен R (что, вероятно, очень редко для оператора ++, но стоит учитывать для максимальной совместимости. )

2

Решение

Есть ли способ, которым я могу обойти это?

Ты можешь использовать boost::has_pre_increment и СФИНАЕ:

#include <string>
#include <boost/type_traits.hpp>template<typename R,bool hasOp = boost::has_pre_increment<R>::value > struct OpRet
{
typedef R Ret;
};
template<typename R> struct OpRet<R,false>
{
typedef void Ret;
};template<typename T>
class Wrapper
{
private:
T val;
public:
Wrapper(T value) : val(value) {}
auto operator++() -> typename OpRet<T>::Ret
{
return ++val;
}
};

int main()
{
Wrapper<std::string> a("abc");
Wrapper<int> b(2);
}

Возможно ли, во-первых, это неправильное поведение для компилятора, так как функция не генерирует код?

Нет. Компилятор выдает правильную диагностику. std::string действительно не имеет префиксного оператора приращения. [temp.deduct] 7 и 8 ясно об этом:

7:

Подстановка происходит во всех типах и выражениях, которые используются в типе функции и в объявлениях параметров шаблона. Выражения включают в себя не только константные выражения, такие как те, которые появляются в границах массива или в качестве нетиповых аргументов шаблона, но также и общие выражения (то есть неконстантные выражения) внутри sizeof, decltype и других контекстов, которые допускают неконстантные выражения. [Примечание: Эквивалентная замена в спецификациях исключений выполняется только при создании экземпляра функции, и в этот момент программа становится плохо сформированной, если в результате замены получается недопустимый тип или выражение. — конец примечания]

8:

Если подстановка приводит к недопустимому типу или выражению, вывод типа завершается неудачей. …

2

Другие решения

Вы действительно хотите СФИНАЕ. operator++ Для этого нужно сделать шаблон функции, и хороший прием — использовать аргумент по умолчанию для параметра шаблона T в зависимый тип (это необходимо для применения SFINAE).

template<typename U = T>
auto operator++() -> decltype(++std::declval<U&>())
{
return ++this->val;
}

Однако, как вы можете заметить, мы теряем удобство использования участника напрямую, и нам нужно немного подумать, чтобы выяснить, к чему именно мы должны обратиться std::declval чтобы получить правильную категорию значений и cv-квалификаторы.

1

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector