Я пытаюсь создать своего рода класс-обертку, который перенаправляет все операторы в содержащийся в нем объект, чтобы попытаться заставить его «притворяться» содержащимся в нем объектом. Код я бы лайк написать выглядит примерно так (упрощенно):
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 (что, вероятно, очень редко для оператора ++, но стоит учитывать для максимальной совместимости. )
Есть ли способ, которым я могу обойти это?
Ты можешь использовать 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:
Если подстановка приводит к недопустимому типу или выражению, вывод типа завершается неудачей. …
Вы действительно хотите СФИНАЕ. operator++
Для этого нужно сделать шаблон функции, и хороший прием — использовать аргумент по умолчанию для параметра шаблона T
в зависимый тип (это необходимо для применения SFINAE).
template<typename U = T>
auto operator++() -> decltype(++std::declval<U&>())
{
return ++this->val;
}
Однако, как вы можете заметить, мы теряем удобство использования участника напрямую, и нам нужно немного подумать, чтобы выяснить, к чему именно мы должны обратиться std::declval
чтобы получить правильную категорию значений и cv-квалификаторы.