简体   繁体   中英

Variadic operator overloading of the [] in C++

I am looking to use the expressions passed in the operator []. I thought that using variatic template arguments would do the trick but i was wrong... Is the a way to do this in c++11?

class object {
 

private:


public:
  void values() { std::cout << "finished" << std::endl; }
  template <typename T, typename... Type> void values(T arg, Type... args) {

    std::cout << arg << "  " << std::endl;
    values(args...);
  }


   template<typename... Type> void operator[](Type... args) {

       values(args...);
  }
};

int main(void) {
  object o1 = object();

  o1.values(1, 6.2, true, "hello"); // Works fine.
  
  o1[1, 6.2, true]; // Only the last value gets printed eg. true
  

  return 0;
}

The broader objective is that I was asked to make a working syntax of this

let o3 = object [ values 1, "2", true, -3.14 ];
let o1 = object [  key("x") = -1, key("y") = -2,values 1, "2", true, -3.14 ]; // no commas are missing

in c++11 using c++11 STL (templates, using, MACROS, operator overloading etc.). I am slowly trying to figure out how to piece this together

First of all, you need to understand that operator[] can only take exactly one argument. Your templated operator hides an error message that is rather clear about that.

struct foo {
    void operator[](int,int);
};

Results in error:

<source>:2:10: error: 'void foo::operator[](int, int)' must have exactly one argument
    2 |     void operator[](int,int);
      |          ^~~~~~~~

You can make it a variadic template, but any instantiation with not exactly one argument isn't right.


Franky, this sounds like a trick question. When you write C++ code you better use C++ syntax. If macros are allowed (they shouldn't be) then almost anything is possible. You just need to be aware that you aren't actually writing C++ anymore. Also, it seems a bit odd to merely ask for some syntax to compile. I don't know javascript and wasn't sure what the two lines are supposed to mean, so I only did that: Make it compile somehow.

Anyhow, lets see what can be done.

let o3 = object [ values 1, "2", true, -3.14 ];

I suppose this declares o3 to be an object whose initializer is retrieved from a container called object that can be indexed via values 1, "2", true, -3.14 . The line can be made to compile by overloading some operator, and #define ing let to be auto and values to construct an object that collects the index (via its operator, ):

For the second line

let o1 = object [  key("x") = -1, key("y") = -2,values 1, "2", true, -3.14 ]; 

a similar trick can be played with operator, and abusing key::operator= . I interpreted key as constructing some key-value pair,eg key("x") = -1 maps the value -1 to the string "x" . What it actually does isn't essential for all the dirty stuff that follows. Once you understood how to misuse operator overloading it can be modified to do something else in the details.

To see that all values are actually passed to operator[] is stole some tuple print function from here: https://stackoverflow.com/a/41171552/4117728

#include <tuple>
#include <string>
#include <typeinfo>
#include <iostream>
#define let auto
//https://stackoverflow.com/a/41171552/4117728
template<class TupType, size_t... I>
void print(const TupType& _tup, std::index_sequence<I...>)
{
    std::cout << "(";
    (..., (std::cout << (I == 0? "" : ", ") << std::get<I>(_tup)));
    std::cout << ")\n";
}

template<class... T>
void print (const std::tuple<T...>& _tup)
{
    print(_tup, std::make_index_sequence<sizeof...(T)>());
}
//........................................

struct Object {
    template <typename ...T>
    struct ObjectIndex {
        ObjectIndex() {}
        ObjectIndex(std::tuple<T...> ind) : index(ind) {}
        std::tuple<T...> index;        
        template <typename U> 
        ObjectIndex<T...,U> operator,(const U& u){ 
            return { std::tuple_cat(index,std::make_tuple(u)) };
        }
        template <typename...U>
        ObjectIndex<T...,U...> operator,(const ObjectIndex<U...>& other) { 
            return { std::tuple_cat(index,other.index) };
        }
    };
    template <typename ...T>
    int operator[](ObjectIndex<T...> index){
        std::cout << typeid(index.index).name() << "\n";
        print(index.index);
        return 42;
    }
};

struct key {
    std::string k;
    int val;
    key(const std::string& s) : k(s) {}
    Object::ObjectIndex<std::string,int> operator=(int v) {
        val = v;
        return {std::make_tuple(k,val)};
    }

};

#define values Object::ObjectIndex<>{} ,

int main() {
    Object object;
    let o3 = object [ values 1, std::string("2"), true, -3.14 ];
    let o1 = object [  key("x") = -1, key("y") = -2,values 1, std::string("2"), true, -3.14 ]; 
}

Live Demo

Don't do this at home (or anywhere else)!

There was some issue with passing a string literal and I didn't bother to look into that further, so I allowed myself to replace "2" with std::string("2") . I guess with some more fighting through endless error messages this can be fixed too.

I have no clue if the code is anywhere near to what those two lines are really supposed to do. I merely took it as a fun exercise to get it compile somehow. When actually returning something from operator[] I ran out of ideas. I have a hard time to imagine a container with a truly variadic operator[] . I choose the answer that is right always.


TL;DR Can you overload operator[] to take more than one parameter? No. Can you overload it to take a single parameter that encapsulates arbitrary values? Sure.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM