Je veux accéder à certaines données de classe en utilisant operator[] mais en fonction du type d'index entre crochets, retourne un type de données ou un autre. À titre d'exemple simplifié:

struct S
{
    int   &operator []( int index ) { std::cout << "[i]"; return i_buffer[index]; }
    short &operator [](short index) { std::cout << "[s]"; return s_buffer[index]; }

private:
    int   i_buffer[10]{ 0,  1,   2,   3,   4,   5,   6,   7,   8,   9  };
    short s_buffer[10]{ 0, 111, 222, 333, 444, 555, 666, 777, 888, 999 };
};

Il n'y a aucun moyen d'écrire un littéral short, donc la seule façon de choisir la surcharge short est de lancer:

S s;
std::cout << s[9] << '\n';        // prints [i]9
std::cout << s[(short)9] << '\n'; // prints [s]999

Mais je n'aime pas ça et je me demandais s'il y avait différentes options.

Qu'est-ce que j'ai essayé?

Paramètre Tagged .

J'ai d'abord essayé d'utiliser des " balises ":

struct S
{
    enum class i_type : std::int32_t {};
    enum class s_type : std::int32_t {};

    int   &operator [](i_type index)
    { std::cout << "[i]"; return i_buffer[static_cast<int>(index)]; }
    short &operator [](s_type index)
    { std::cout << "[s]"; return s_buffer[static_cast<int>(index)]; }

private:
    int   i_buffer[10]{ 0,  1,   2,   3,   4,   5,   6,   7,   8,   9  };
    short s_buffer[10]{ 0, 111, 222, 333, 444, 555, 666, 777, 888, 999 };
};

Cela fonctionne mais est encore un peu verbeux:

S s;
std::cout << s[9] << '\n';            // error, no possible overload to be taken
std::cout << s[S::i_type{9}] << '\n'; // prints [i]9
std::cout << s[S::s_type{9}] << '\n'; // prints [s]999

Modèle.

En guise de solution de contournement folle, je voulais essayer de modéliser l'opérateur:

struct S
{
    template <typename T>
    T &operator [](T) { std::cout << "???"; return 0; }

private:
    int   i_buffer[10]{ 0,  1,   2,   3,   4,   5,   6,   7,   8,   9  };
    short s_buffer[10]{ 0, 111, 222, 333, 444, 555, 666, 777, 888, 999 };
};

template <>
int   &S::operator [](int index)   { std::cout << "[i]"; return i_buffer[index]; }
template <>
short &S::operator [](short index) { std::cout << "[s]"; return s_buffer[index]; }

La version du modèle se comporte comme le code d'origine, mais il n'y a pas de moyen simple de spécifier un paramètre de type avec operator[]:

S s;
std::cout << s[9] << '\n';        // prints [i]9 like before
std::cout << s[(short)9] << '\n'; // prints [s]999 like before
std::cout << s<short>[9] << '\n'; // s is not template
std::cout << s[9]<short> << '\n'; // nonsense
// Correct but utterly verbose and hard to write and read
std::cout << s.operator[]<short>(9) << '\n';

Question.

Tous les problèmes décrits se produisent également avec operator(), je veux savoir s'il existe d'autres alternatives dont je ne suis pas au courant?

6
PaperBirdMaster 25 janv. 2017 à 13:51

4 réponses

Meilleure réponse

Je pense qu'utiliser une méthode nommée est une bien meilleure idée que d'utiliser operator[] dans votre situation, car il serait plus facile de comprendre que deux tampons séparés sont accessibles en lisant le code source.

Quoi qu'il en soit, si vous souhaitez utiliser votre approche operator[], vous pouvez utiliser caractères de caractères forts et littéraux définis par l'utilisateur pour avoir une sécurité de type avec une surcharge syntaxique minimale:

BOOST_STRONG_TYPEDEF(std::size_t, int_index)
BOOST_STRONG_TYPEDEF(std::size_t, short_index)

struct S
{
    auto& operator[](int_index i) { /* ... */ }
    auto& operator[](short_index i) { /* ... */ }
};

auto operator "" _ii(unsigned long long int x) { return int_index{x}; }
auto operator "" _si(unsigned long long int x) { return short_index{x}; }

Vous pouvez ensuite appeler vos méthodes comme suit:

S s;

auto& some_int = s[15_ii];
auto& some_short = s[4_si];

exemple de boîte à baguette

7
Vittorio Romeo 25 janv. 2017 à 10:57

Je suis d'accord avec Vittorio Romeo que la meilleure solution est une méthode nommée.

Cependant voici une solution:

template <class T> struct S_proxy {
  T* data; 
  T& operator[](std::size_t i) { return data[i]; }
};

struct S
{
    auto i_type() { return S_proxy<int>{i_buffer}; };
    auto s_type() { return S_proxy<short>{s_buffer}; };

private:
    int   i_buffer[10]{ 0,  1,   2,   3,   4,   5,   6,   7,   8,   9  };
    short s_buffer[10]{ 0, 111, 222, 333, 444, 555, 666, 777, 888, 999 };
};

Et utilise:

S s;
return s.s_type()[2];
1
Community 23 mai 2017 à 10:29

Si i_type et s_type sont supposés avoir une signification par eux-mêmes, il est possible d'ajouter une sémantique aux opérateurs []. Quelque chose comme

#include <iostream>

struct Month {
    explicit Month(int m)
        : m(m)
    {
    }
    int m;
};

struct Day {
    explicit Day(short d)
        : d(d)
    {
    }
    short d;
};

struct S {
    int& operator[](const Month& mes)
    {
        std::cout << "[i]";
        return i_bufer[mes.m];
    }
    short& operator[](const Day& dis)
    {
        std::cout << "[s]";
        return s_bufer[dis.d];
    }

private:
    int i_bufer[10]{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    short s_bufer[10]{ 0, 111, 222, 333, 444, 555, 666, 777, 888, 999 };
};

int main()
{
    S s;
    std::cout << s[Month(9)] << '\n'; // muestra [i]9
    std::cout << s[Day(9)] << '\n'; // muestra [s]999
}
0
Loreto 26 janv. 2017 à 13:23

Je pense que j'utiliserais std::tie de la bibliothèque <tuple>, puis j'écrirais une petite aide pour trouver le type de référence correct:

#include <tuple>
#include <iostream>

template<class As, class...Ts>
auto& as(std::tuple<const Ts&...>ts)
{
    return std::get<As const&>(ts);
};

template<class As, class...Ts>
auto& as(std::tuple<Ts&...>ts)
{
    return std::get<As &>(ts);
};

struct S
{
    // both cost and mutable version provided for completeness.

    auto operator[](std::size_t i) const {
        return std::tie(i_buffer[i], s_buffer[i]);
    }

    auto operator[](std::size_t i) {
        return std::tie(i_buffer[i], s_buffer[i]);
    }

private:
    int   i_buffer[10]{ 0,  1,   2,   3,   4,   5,   6,   7,   8,   9  };
    short s_buffer[10]{ 0, 111, 222, 333, 444, 555, 666, 777, 888, 999 };
};

int main()
{
    auto s = S();
    const auto x = S();

    std::cout << "short is : " << as<short>(s[5])<< '\n';
    std::cout << "int is : " << as<int>(s[5])<< '\n';

    std::cout << "short is : " << as<short>(x[6])<< '\n';
    std::cout << "int is : " << as<int>(x[6])<< '\n';
}

De cette façon, le code est explicite mais toujours succinct.

Production attendue:

short is : 555
int is : 5
short is : 666
int is : 6

Après avoir lu les autres commentaires, je pourrais choisir de stocker la matrice sous une forme (disons) par ligne, puis de fournir un wrapper par couleur.

Un exemple à peine fonctionnel:

#include <tuple>
#include <iostream>
#include <array>


template<std::size_t Rows, std::size_t Cols>
struct RowWiseMatrix
{

    auto& operator[](std::size_t i) { return data_[i]; }

    std::array<std::array<double, Cols>, Rows> data_;
};

template<std::size_t Rows, std::size_t Cols>
struct ColumnProxy
{
    ColumnProxy(std::array<std::array<double, Cols>, Rows>& data, std::size_t col)
            : data_(data), col_(col)
    {

    }

    auto& operator[](std::size_t i) { return data_[i][col_]; }

    std::array<std::array<double, Cols>, Rows>& data_;
    std::size_t col_;
};


template<std::size_t Rows, std::size_t Cols>
struct ColWiseProxy
{
    ColWiseProxy(RowWiseMatrix<Rows, Cols>& mat) : underlying_(mat) {}

    auto operator[](std::size_t i) { return ColumnProxy<Rows, Cols> { underlying_.data_, i }; }

    RowWiseMatrix<Rows, Cols>& underlying_;
};


template<std::size_t Rows, std::size_t Cols>
auto& rowWise(RowWiseMatrix<Rows, Cols>& mat)
{
    return mat;
};

template<std::size_t Rows, std::size_t Cols>
auto colWise(RowWiseMatrix<Rows, Cols>& mat)
{
    return ColWiseProxy<Rows, Cols>(mat);
};

int main()
{
    auto m = RowWiseMatrix<3, 3> {
            std::array<double, 3>{ 1, 2, 3 },
            std::array<double, 3>{ 4, 5, 6},
            std::array<double, 3>{ 7, 8, 9}
    };

    std::cout << rowWise(m)[0][2] << '\n';
    std::cout << colWise(m)[0][2] << '\n';
}

Production attendue:

3
7
2
Richard Hodges 25 janv. 2017 à 11:30