0

I'm using boost::variant already for a while, but there are still some questions that leave me puzzled.

The following code is not compiling, as I'm trying to store a std::pair<int, int> to a boost::variant, that can only contain int, double and std::string. Still I need the function convert, but it should throw an exception in case I'm trying to store a type in the boost::variant that it will not fit.

#include <iostream>
#include <boost/variant.hpp>
#include <vector>

typedef boost::variant<int, double, std::string> TVar;

template<typename T>
std::vector<TVar> convert(const std::vector<T>& vec) {  
    std::vector<TVar> ret;
    for (size_t t = 0; t < vec.size(); t++) {
        ret.push_back(vec[t]);
    }
    return ret;
}

int main(int arg, char** args) {
    {
        std::vector<double> v = { 3,6,4,3 };
        auto ret=convert(v);
        std::cout << ret.size() << std::endl;
    }
    {
        std::vector<bool> v = { true, false, true };
        auto ret=convert(v);
        std::cout << ret.size() << std::endl;
    }
    {
        std::vector<std::pair<int, int>> v = { std::make_pair<int, int>(5,4) };
        auto ret = convert(v);
        std::cout << ret.size() << std::endl;
    }
    return 0;
}

It seems, that std::enable_if might be part of the solution, but I was unable to bring this to an end.

Aleph0
  • 5,816
  • 4
  • 29
  • 80

2 Answers2

4

The following code is not compiling, as I'm trying to store a std::pair<int, int> to a boost::variant, that can only contain int, double and std::string. Still I need the function convert, but it should throw an exception in case I'm trying to store a type in the boost::variant that it will not fit.

This is not actually something that you should want, you should be happier with the behavior that you have.

If you are trying to store a type in a boost::variant that it will not fit, that is a problem in your program that will be obvious to the compiler at compile-time -- the compiler has no idea how to proceed with the operation, there's literally no reasonable code available for it to emit for this. It knows the type that you are trying to store, and it knows what kind of variant you have, and it can see that there is no conversion.

When the compiler rejects the code and tells you there is no conversion, it is telling you about the problem right away, as soon as it learns about the issue.

If it merely threw an exception at run-time whenever the conversion was attempted, that would mean you would not discover the issue until later in testing. That's much more annoying and expensive -- it means that the compiler knew or should have known about the bad conversion issue, but decided to cover it up.

Usually in C++, programmers attempt to write code such that any common mistakes will manifest as compile-time errors rather than run-time errors, for this reason. boost::variant is written with this idea in mind.

So, I think you should revisit the premise of the question and try to explain what it is you are actually trying to do.

Chris Beck
  • 15,614
  • 4
  • 51
  • 87
  • Actually, the idea of throwing an exception was only for the example code to keep it as small as possible. In my case I need to handle different types in a different fashion to avoid code duplication. As to say `T` can be a simple datatype like `int`, `std::string` or `double` that can be processed in a unified way. In case `T` is a complex datatype like `Matrix` or `Quaternion` i need to do something special for each complex datatype. – Aleph0 Jun 06 '16 at 08:13
  • @FrankSimon: I see. So one alternate approach to the other one (using `std::is_same` once for each type in the variant and taking the or), you could try using `std::is_constructible` and just ask if the variant is constructible from the type. The drawback is that some kinds of conversions would then be permitted, so this test will be more lax than the other. But that might be a good thing. It's also much easier to implement. HTH – Chris Beck Jun 06 '16 at 18:26
1

Here is my approach, using SFINAE. It's not the best solution, because you would have to change the test every time you add/remove a type from TVar, but it works:

//Using SFINAE to check if type can be converted into TVar
template<typename T, typename = std::enable_if_t<std::is_same<TVar::types::item, T>::value
    || std::is_same<TVar::types::next::item, T>::value
    || std::is_same<TVar::types::next::next::item, T>::value>>
std::vector<TVar> convert(const std::vector<T>& vec) {
    std::vector<TVar> ret;
    for (size_t t = 0; t < vec.size(); t++) {
        ret.push_back(vec[t]);
    }
    return ret;
}

std::vector<std::nullptr_t> convert(...)
{
    //throw Something
    return{};
}

Little explanation:

boost::variant has an internal type, called types. It is basically a compile time linked list, where the type of each element is in type.

The condition for std::enable_if_t is

std::is_same<TVar::types::item, T>::value /*is T same as the first type in variant? */
|| std::is_same<TVar::types::next::item, T>::value /* is T same as second type */
|| std::is_same<TVar::types::next::next::item, T>::value /* is T same as third type */

So, the template convert is only taken if one of the above conditions is true, which it would be for the types in the boost::variant.

Rakete1111
  • 47,013
  • 16
  • 123
  • 162
  • Hi. Many thanks for this nice looking solution. Unfortunately, it doesn't had the right behavior for me. I'm always falling into the second part of the `convert(...)` function. The program outputs three times a `0` then. – Aleph0 Jun 06 '16 at 08:16
  • @FrankSimon sorry, didn't notice this bug :) Now it works, but it's not nice to look at... – Rakete1111 Jun 06 '16 at 08:26
  • Wow. That really gives me the creeps. Unfortunately, it's not compiling with MSVC 2015? It says: `error C2275: "TVar": Invalid usage of type as expression` Any idea, what happened here? – Aleph0 Jun 06 '16 at 08:29
  • @FrankSimon And now? Sorry again. It compiles for me with VS2015 :) – Rakete1111 Jun 06 '16 at 08:30
  • Many thanks. It now works, but it gives me really a headache. :-) I'll need some time to understand this solution. – Aleph0 Jun 06 '16 at 08:32
  • Seems that this solution is fitting in case of this special `boost::variant` having exactly three types. What could be the more general solution? Is it possible to iterate over all three types with `mpl::for_each`? – Aleph0 Jun 06 '16 at 08:34
  • @FrankSimon `mpl::for_each` is not a compile time solution, unfortunately. I'll try to find something better – Rakete1111 Jun 06 '16 at 08:37
  • I'll guess there is none. I'm still very satisfied with this solution and a non-compile time solution is also okay for me. It seems, that a part of the solution is already in one of my earlier posts: http://stackoverflow.com/q/35357614/5762796 I can work it out on my own now. Many thanks, you really helped me a lot. – Aleph0 Jun 06 '16 at 08:46