EDIT: thanks to the answers I was able to solve all the issues with my code. I post here the solution: it might be useful to somebody in the future. In particular, the suggestion of using a proxy class proved very useful! The example doens't consider all the cases but it should be trivial to add another type to the variant!
I am writing a C++ (C11 - Linux) custom class that sort of behaves like an unordered map {key, value}. I would like to overload the [] operator so that I can use the class with the same syntax as an unordered map: object[key] would return value.
The problem is that I need object[key] to return a variant type. I can store internally value as a string or struct but, when I retrieve it by using object[key], I need the returned value to be an int, float or string depending on some internal condition determined at runtime.
This is why I was thinking about using the boost::variant library ... but I am opened to any other suggestion. The only restriction is that the test class (in the example) have to compiled as a shared library .so and that the code must be C11 compatible (I mean compilable by GNU g++ 4.8.5).
I wrote a simple example to show what kind of behavior I would like The example is not meant to mean anything. It is just to illustrate the kind of error that I am getting. The real class that I am writing has a different structure but the usage of bool::variant and operator [] overload is the same.
test.cpp
#include <boost/variant.hpp>
typedef boost::variant<int, float> test_t;
class Test
{
int i ;
float f;
void set(int randomint, test_t tmp){
if ( randomint == 0 ) i = boost::get<int>(tmp);
else f = boost::get<float>(tmp);
}
test_t get(int randomint){
if ( randomint == 0 ) return i;
else return f;
}
struct IntOrFloat {
int randomint;
Test *proxy;
explicit operator int () const
{ return boost::get<int>(proxy->get(randomint)); }
void operator= (int tmp)
{ proxy->set(randomint, tmp); }
explicit operator float () const
{ return boost::get<float>(proxy->get(randomint)); }
void operator= (float tmp)
{ proxy->set(randomint, tmp); }
};
public:
IntOrFloat operator [](int randomint)
{ return IntOrFloat{randomint, this}; }
const IntOrFloat operator [](int randomint) const
{ return IntOrFloat{randomint, (Test *) this}; }
};
main.cpp
#include <iostream>
#include <boost/variant.hpp>
#include "test.cpp"
#define INTEGER 0
#define FLOAT 1
int main (void) {
Test test;
int i = 3;
float f = 3.14;
test[INTEGER] = i;
test[FLOAT] = f;
int x = (int) test[INTEGER];
float y = (float) test[FLOAT];
std::cout << x << std::endl;
std::cout << y << std::endl;
return 0;
}
To compile and run
g++ -fPIC -std=c++11 -shared -rdynamic -o test.so test.cpp
g++ -std=c++11 -o test main.cpp -Lpath/to/the/test.so -l:test.so
LD_LIBRARY_PATH="path/to/the/test.so" ./test
In C++, overload resolution does not happen on the return type, so given
int foo() { return 0; }
float foo() { return 0.f; }
there is no sanctioned way for the compiler to differentiate
int x = foo();
float f = foo();
. There is a trick using conversion operator overloads:
#include <iostream>
struct IntOrFloat {
operator int () const {
std::cout << "returning int\n";
return 0;
}
operator float () const {
std::cout << "returning float\n";
return 0.f;
}
};
IntOrFloat foo() { return IntOrFloat(); }
int main () {
int x = foo();
float f = foo();
}
You can force more verbosity by making the conversion explicit
:
explicit operator int () const ...
explicit operator float () const ...
int x = static_cast<int>(foo());
int x = float(foo()); // old-style-cast
This proxy (or other conversion operator tricks) are as far as you'll to simulate return type overload resolution.
The idea once arised while searching a solution to supporting <euclidian vector> * <euclidian vector>
-syntax, i.e. an operator*
which either means dot product or vector product, depending on the type of the variable the product is assigned to.
In the end, it was not really practical and did not contribute positively to readability. The more verbose forms dot(vec, vec)
and cross(vec, vec)
were superior for several reasons, among which:
temporal and/or spatial locality: you are essentially returning a closure with code in it, which can be executed many times at many places. this can be doubly bad as it does not (actually, does) work well with auto &
kind of declarations:
int main () {
const auto &f = foo();
const int g = f;
const int h = f;
std::cout << (int)f << "\n";
}
This prints something multiple times, going hand in hand with the least surprise principle. Of course this becomes less severe if your proxy basically just forwards readily available values. But the error messages won't become any better!
Note you can also incorporate template conversion operator overloads and wild metaprogramming. While worth a fun experiment, this is not something I'd love to put into a production code base, for maintenance and readability will even decrease.
What remains? Infinite possibilities; but some of the most feasible:
std::tuple
, which comes with conversion operators in case of distinct member types)