Ошибка преобразования Boost Variant при использовании посетителя

Я не имею в виду дамп кода, но это действительно самый маленький воспроизводимый пример, который я мог создать, даже после удаления всей логики, чтобы сделать его более понятным.

По сути, я пытаюсь реализовать свою собственную версию некоторых базовых типов в C++, чтобы имитировать динамическую типизацию, сохраняя типы в boost::variant с именем Values и используя boost::static_visitor для выполнения операций над вариантом Value. Одна операция, которую я пытаюсь реализовать, — это оператор not equals, и для этого я создал посетителя с именем Not_Equal. Оператор Not_Equal использует SFINAE со структурой low_priority и high_priority, чтобы определить, разрешены ли два типа, используемые в операции.

Типы в варианте Values: ​​{SpecialInt, SpecialBoolean, std::shared_ptr<SeriesInt>, std::shared_ptr<SeriesBoolean>}. Причина, по которой SeriesInt и SeriesBoolean являются умными указателями, заключается в том, что они хранят много информации в моей реальной версии, поэтому их копирование будет дорогим.

Допустимые операторы != следующие:

SpecialInt != SpecialInt
SpecialBoolean != SpecialBoolean
SeriesInt != SpecialInt
SeriesInt != SeriesInt
SeriesBoolean != SeriesBoolean

как представлено перегрузками операторов в каждом классе.

#include <memory>
#include <boost/variant/static_visitor.hpp>
#include <boost/variant.hpp>

class SpecialBoolean  {
public:

    SpecialBoolean(bool val) {} //removing this line fixes it
    SpecialBoolean()  {}

    SpecialBoolean operator!= (const SpecialBoolean& rhs) const {
        return *this;
    }

};

class SpecialInt {
public:

    SpecialInt(float val) {} //removing this line fixes it
    SpecialInt() {}

    SpecialBoolean operator!= (const SpecialInt& rhs) const {
        return SpecialBoolean();
    }

};

class SeriesBoolean {
public:

    SeriesBoolean() {}

    std::shared_ptr<SeriesBoolean> operator!= (const SpecialBoolean& rhs) const {
        return std::make_shared<SeriesBoolean>();
    }
    std::shared_ptr<SeriesBoolean> operator!= (const SeriesBoolean& rhs) const {
        return std::make_shared<SeriesBoolean>();
    }

};

class SeriesInt {
public:
    
    SeriesInt() {}
    
    std::shared_ptr<SeriesBoolean> operator!= (const SpecialInt& rhs) const {
        return std::make_shared<SeriesBoolean>();
    }
    std::shared_ptr<SeriesBoolean> operator!= (const SeriesInt& rhs) const {
        return std::make_shared<SeriesBoolean>();
    }

};

typedef boost::variant <SpecialInt, SpecialBoolean, std::shared_ptr<SeriesInt>, std::shared_ptr<SeriesBoolean> > Values;

struct low_priority {};
struct high_priority : low_priority {};

struct Not_Equal : public boost::static_visitor<Values> {

    auto operator() (Values const& a, Values const& b) const {
        return boost::apply_visitor(*this, a, b);
    }

    template <typename T, typename U>
    auto operator() (high_priority, T a, U b) const -> decltype(Values(a != b)) {
        return a != b; // problem here
    }

    template <typename T, typename U>
    auto operator() (high_priority, std::shared_ptr<T> a, std::shared_ptr<U> b) const -> decltype(Values(*a != *b)) {
        return *a != *b;
    }

    template <typename T, typename U>
    auto operator() (high_priority, std::shared_ptr<T> a, U b) const -> decltype(Values(*a != b)) {
        return *a != b;
    }

    template <typename T, typename U>
    Values operator() (low_priority, T, U) const {
        throw std::runtime_error("Incompatible arguments");
    }

    template <typename T, typename U>
    Values operator() (T a, U b) const {
        return (*this)(high_priority{}, a, b);
    }
};

Проблема возникает у посетителя в строке return a != b; , где типы операторов не являются shared_ptr и, следовательно, либо SpecialInt, либо SpecialBoolean, что вызывает ошибки:

Ошибка C2446 '!=': нет преобразования из 'SeriesInt *' в 'SeriesBoolean *'

Ошибка C2446 '!=': нет преобразования из 'SeriesBoolean *' в 'SeriesInt *'

Я не понимаю, какое это имеет отношение к SeriesBoolean* или SeriesInt*, поскольку он может принимать только типы SpecialInt и SpecialBoolean, но я заметил, что когда я удаляю конструкторы, которые принимают аргумент в SpecialInt и SpecialBoolean, код компилируется и запускается как обычно. Мне нужны эти конструкторы для загрузки значений в классы (логика удалена), поэтому мой вопрос: почему я получаю эти ошибки и как я могу это исправить?

Трюк high_priority не SFINAE (а больше похож на отправку тегов). Кроме того, вам это не нужно здесь, см. мой пример.

sehe 14.12.2020 00:03
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
1
153
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Конструкторы для ваших типов приводят к неоднозначным инициализаторам вариантов.

Если можете, подумайте о том, чтобы сделать их явными.

Кроме того, типы возврата decltype на самом деле не имеют смысла, поскольку посетитель возвращает Values по определению.

Эта перегрузка

template <typename T, typename U>
Values operator()(high_priority, T a, U b) const {
    return a != b; // problem here
}

соответствует ВСЕМ комбинациям. operator != в таких случаях /не определено/. Вы хотели сделать:

template <typename T>
Values operator()(high_priority, T const& a, T const& b) const {
    return a != b; // problem here
}

Теперь вам также понадобится следующая перегрузка:

template <typename T>
Values operator()(high_priority, std::shared_ptr<T> const& a, std::shared_ptr<T> const& b) const {
    return *a != *b;
}

В противном случае два идентичных типа аргумента shared_pointer будут неоднозначными.

Эта перегрузка кажется неправильной:

template <typename T, typename U>
Values operator()(high_priority, std::shared_ptr<T> const& a, std::shared_ptr<U> const& b) const {
    return *a != *b;
}

Это, очевидно, приведет к проблемам, потому что, например. сравните SeriesInt с SeriesBool, который не реализован. Поскольку его нет в вашем списке, отбросьте его.

Точно так же, поскольку

template <typename T, typename U>
Values operator()(high_priority, std::shared_ptr<T> const& a, U const& b) const {
    return *a != b;
}

также соответствует, например, [ T = SeriesInt, U = SpecialBoolean ], он не будет компилироваться.

УПРОЩАТЬ!

Я бы в основном просмотрел список поддерживаемых перегрузок и просто явно реализовал их. Я буду использовать приведенные выше шаблоны только для случаев 1:1.

Обратите внимание, что последовательное (!) использование аргументов с помощью const& делает выполнение намного более эффективным, особенно для общих указателей.

struct Not_Equal : boost::static_visitor<Values> {
    Values operator()(Values const& a, Values const& b) const {
        return boost::apply_visitor(*this, a, b);
    }

    // SpecialInt     != SpecialInt
    // SpecialBoolean != SpecialBoolean
    template <typename T>
    Values operator()(T const& a, T const& b) const {
        return a != b;
    }

    // SeriesInt      != SeriesInt
    // SeriesBoolean  != SeriesBoolean
    template <typename T>
    Values operator()(std::shared_ptr<T> const& a, std::shared_ptr<T> const& b) const {
        return *a != *b;
    }

    // SeriesInt      != SpecialInt
    Values operator()(std::shared_ptr<SeriesInt> const& a, SpecialInt const& b) const {
        return *a != b;
    }

    template <typename... T>
    Values operator()(T const&...) const {
        throw std::runtime_error("Incompatible arguments");
    }
};

Живая демонстрация

Прямой эфир на Колиру

#include <boost/variant.hpp>
#include <boost/variant/static_visitor.hpp>
#include <memory>

struct SpecialBoolean {
    explicit SpecialBoolean(bool /*val*/ = false) {}

    SpecialBoolean operator!=(const SpecialBoolean& /*rhs*/) const { return *this; }
};

struct SpecialInt {
    explicit SpecialInt(float /*val*/ = 0) {}

    SpecialBoolean operator!=(const SpecialInt& /*rhs*/) const {
        return SpecialBoolean();
    }
};

struct SeriesBoolean {
    SeriesBoolean() {}

    std::shared_ptr<SeriesBoolean> operator!=(const SpecialBoolean& /*rhs*/) const {
        return std::make_shared<SeriesBoolean>();
    }
    std::shared_ptr<SeriesBoolean> operator!=(const SeriesBoolean& /*rhs*/) const {
        return std::make_shared<SeriesBoolean>();
    }
};

struct SeriesInt {
    SeriesInt() {}

    std::shared_ptr<SeriesBoolean> operator!=(const SpecialInt& /*rhs*/) const {
        return std::make_shared<SeriesBoolean>();
    }
    std::shared_ptr<SeriesBoolean> operator!=(const SeriesInt& /*rhs*/) const {
        return std::make_shared<SeriesBoolean>();
    }
};

typedef boost::variant<
    SpecialInt,
    SpecialBoolean,
    std::shared_ptr<SeriesInt>,
    std::shared_ptr<SeriesBoolean>
  >
  Values;

struct Not_Equal : boost::static_visitor<Values> {
    Values operator()(Values const& a, Values const& b) const {
        return boost::apply_visitor(*this, a, b);
    }

    // SpecialInt     != SpecialInt
    // SpecialBoolean != SpecialBoolean
    template <typename T>
    Values operator()(T const& a, T const& b) const {
        return a != b;
    }

    // SeriesInt      != SeriesInt
    // SeriesBoolean  != SeriesBoolean
    template <typename T>
    Values operator()(std::shared_ptr<T> const& a, std::shared_ptr<T> const& b) const {
        return *a != *b;
    }

    // SeriesInt      != SpecialInt
    Values operator()(std::shared_ptr<SeriesInt> const& a, SpecialInt const& b) const {
        return *a != b;
    }

    template <typename... T>
    Values operator()(T const&...) const {
        throw std::runtime_error("Incompatible arguments");
    }
};

int main() {
}

БОНУС

Кроме того, я думаю, вам следует инкапсулировать оптимизацию использования shared_ptr<>, исключив все ваши особые случаи.

Это упрощает все вышеперечисленное:

struct Not_Equal : boost::static_visitor<Values> {
    Values operator()(Values const& a, Values const& b) const {
        return boost::apply_visitor(*this, a, b);
    }

    template <typename T>
    Values operator()(T const& a, T const& b) const { return a != b; }
    Values operator()(SeriesInt const& a, SpecialInt const& b) const { return a != b; }
    Values operator()(SeriesBoolean const& a, SpecialBoolean const& b) const { return a != b; }

    template <typename... T>
    Values operator()(T const&...) const {
        throw std::runtime_error("Incompatible arguments");
    }
};

Вот полная демонстрация с тестовыми примерами для этого Live On Coliru

#include <boost/variant.hpp>
#include <boost/variant/static_visitor.hpp>
#include <memory>
#include <vector>
#include <iostream>
#include <iomanip>

struct SpecialBoolean {
    explicit SpecialBoolean(bool val = false) : val(val) {}
    SpecialBoolean operator!=(const SpecialBoolean& rhs) const { return SpecialBoolean{val != rhs.val}; }
  private:
    bool val;
    friend std::ostream& operator<<(std::ostream& os, SpecialBoolean const& b) { return os << "SpecialBoolean{" << std::boolalpha << b.val << "}"; }
};

struct SpecialInt {
    explicit SpecialInt(float val = false) : val(val) {}
    SpecialBoolean operator!=(const SpecialInt& rhs) const { return SpecialBoolean{val != rhs.val}; }
  private:
    float val;
    friend std::ostream& operator<<(std::ostream& os, SpecialInt const& i) { return os << "SpecialInt{" << i.val << "}"; }
};

struct SeriesBoolean {
    SeriesBoolean operator!=(const SpecialBoolean& /*rhs*/) const { return {}; } // TODO
    SeriesBoolean operator!=(const SeriesBoolean& /*rhs*/) const { return {}; } // TODO

  private:
    struct VeryLarge {
        std::array<SpecialBoolean, 512> _it_is;
    };
    std::shared_ptr<VeryLarge> _data = std::make_shared<VeryLarge>();
    friend std::ostream& operator<<(std::ostream& os, SeriesBoolean const&) { return os << "SeriesBoolean{...}"; }
};

struct SeriesInt {
    SeriesBoolean operator!=(const SpecialInt& /*rhs*/) const { return {}; }
    SeriesBoolean operator!=(const SeriesInt& /*rhs*/) const { return {}; }

  private:
    struct VeryLarge {
        std::array<SpecialInt, 512> _it_is;
    };
    std::shared_ptr<VeryLarge> _data = std::make_shared<VeryLarge>();
    friend std::ostream& operator<<(std::ostream& os, SeriesInt const&) { return os << "SeriesInt{...}"; }
};

using Values = boost::variant< SpecialInt, SpecialBoolean, SeriesInt, SeriesBoolean >;

struct Not_Equal : boost::static_visitor<Values> {
    Values operator()(Values const& a, Values const& b) const {
        return boost::apply_visitor(*this, a, b);
    }

    template <typename T>
    Values operator()(T const& a, T const& b) const { return a != b; }
    Values operator()(SeriesInt const& a, SpecialInt const& b) const { return a != b; }
    Values operator()(SeriesBoolean const& a, SpecialBoolean const& b) const { return a != b; }

    template <typename... T>
    Values operator()(T const&...) const {
        throw std::runtime_error("Incompatible arguments");
    }
};

int main() {
    Values const vv[] = {
        SpecialInt(42),
        SpecialInt(-314e-2),
        SpecialBoolean(false),
        SpecialBoolean(true),
        SeriesInt(),
        SeriesBoolean()
    };

    Not_Equal const neq;

    auto col = [](auto const& v, bool right = false) -> auto& {
        std::ostringstream ss; // just for quick formatting
        ss << v;

        if (right)
            std::cout << std::right;
        else
            std::cout << std::left;
        return std::cout << std::setw(21) << ss.str();
    };

    for (auto const& a: vv) for (auto const& b: vv) try {
        col(a, true) << " != ";
        col(b) << " --> ";
        col(neq(a, b)) << "\n";
    } catch(std::exception const& e) {
        col(e.what()) << "\n";
    }
}

Печать

       SpecialInt{42} != SpecialInt{42}        --> SpecialBoolean{false}
       SpecialInt{42} != SpecialInt{-3.14}     --> SpecialBoolean{true} 
       SpecialInt{42} != SpecialBoolean{false} --> Incompatible arguments
       SpecialInt{42} != SpecialBoolean{true}  --> Incompatible arguments
       SpecialInt{42} != SeriesInt{...}        --> Incompatible arguments
       SpecialInt{42} != SeriesBoolean{...}    --> Incompatible arguments
    SpecialInt{-3.14} != SpecialInt{42}        --> SpecialBoolean{true} 
    SpecialInt{-3.14} != SpecialInt{-3.14}     --> SpecialBoolean{false}
    SpecialInt{-3.14} != SpecialBoolean{false} --> Incompatible arguments
    SpecialInt{-3.14} != SpecialBoolean{true}  --> Incompatible arguments
    SpecialInt{-3.14} != SeriesInt{...}        --> Incompatible arguments
    SpecialInt{-3.14} != SeriesBoolean{...}    --> Incompatible arguments
SpecialBoolean{false} != SpecialInt{42}        --> Incompatible arguments
SpecialBoolean{false} != SpecialInt{-3.14}     --> Incompatible arguments
SpecialBoolean{false} != SpecialBoolean{false} --> SpecialBoolean{false}
SpecialBoolean{false} != SpecialBoolean{true}  --> SpecialBoolean{true} 
SpecialBoolean{false} != SeriesInt{...}        --> Incompatible arguments
SpecialBoolean{false} != SeriesBoolean{...}    --> Incompatible arguments
 SpecialBoolean{true} != SpecialInt{42}        --> Incompatible arguments
 SpecialBoolean{true} != SpecialInt{-3.14}     --> Incompatible arguments
 SpecialBoolean{true} != SpecialBoolean{false} --> SpecialBoolean{true} 
 SpecialBoolean{true} != SpecialBoolean{true}  --> SpecialBoolean{false}
 SpecialBoolean{true} != SeriesInt{...}        --> Incompatible arguments
 SpecialBoolean{true} != SeriesBoolean{...}    --> Incompatible arguments
       SeriesInt{...} != SpecialInt{42}        --> SeriesBoolean{...}   
       SeriesInt{...} != SpecialInt{-3.14}     --> SeriesBoolean{...}   
       SeriesInt{...} != SpecialBoolean{false} --> Incompatible arguments
       SeriesInt{...} != SpecialBoolean{true}  --> Incompatible arguments
       SeriesInt{...} != SeriesInt{...}        --> SeriesBoolean{...}   
       SeriesInt{...} != SeriesBoolean{...}    --> Incompatible arguments
   SeriesBoolean{...} != SpecialInt{42}        --> Incompatible arguments
   SeriesBoolean{...} != SpecialInt{-3.14}     --> Incompatible arguments
   SeriesBoolean{...} != SpecialBoolean{false} --> SeriesBoolean{...}   
   SeriesBoolean{...} != SpecialBoolean{true}  --> SeriesBoolean{...}   
   SeriesBoolean{...} != SeriesInt{...}        --> Incompatible arguments
   SeriesBoolean{...} != SeriesBoolean{...}    --> SeriesBoolean{...}   

Кроме того, я думаю, вам следует инкапсулировать оптимизацию использования shared_ptr<>, исключив все ваши особые случаи, в комплекте с 36 тестовыми примерами

sehe 14.12.2020 00:59

Как ни странно, с упрощениями можно вдруг просто SFINAE на a!=b: coliru.stacked-crooked.com/a/ed58cdebf04af047

sehe 14.12.2020 01:09

Ваше здоровье. У меня была небольшая опечатка в реализации operator<<. исправлено и вариант Sfinae тоже

sehe 14.12.2020 16:23

Замечено! Вы предложили много хороших изменений, которые значительно облегчают работу, например, инкапсуляцию файла shared_ptr. Не могу поверить, что я не подумал об этом. Просто вопрос, почему вы рекомендуете использовать явное для конструктора?

Tom 14.12.2020 16:28

Это позволяет избежать неожиданных неявных преобразований. Вы не столкнетесь с этим легко после упрощений, но посетитель склонен к ним (потому что первая перегрузка требует (Values const&, Values const&) приглашения неожиданных конверсий. Примечание: вы можете избежать этой путаницы, отделив отправку вариантов от ваших перегрузок посетителя. Я столкнулся с что недавно здесь, где я также объясняю, как я обычно исправляю/избегаю этого.

sehe 14.12.2020 16:34

Спасибо. Также вы упомянули, что лучше всего для производительности передавать std::shared_ptr как const&. Является ли это специфичным для shared_ptr (и других интеллектуальных указателей), потому что это не модуль и, следовательно, имеет дорогостоящие операции копирования?

Tom 14.12.2020 16:57

«дорого» относительно, но да: не-тривиально означает, что накладные расходы могут быть больше, чем вам нравится, особенно на платформах, где счетчик ссылок не является безблокировочным (объявление 1). По сути, если вы не собираетесь делиться (и сразу отменять) право собственности, либо передайте shared_ptr<T> const&, либо, еще лучше, просто T const& :)

sehe 14.12.2020 17:56

благослови вас за всю помощь, которую вы оказали, я очень ценю это!

Tom 14.12.2020 20:47

Другие вопросы по теме