0

I've a class withmap<string,vector<string>>. I want to give a user a member function to receive the value for a key in different formats than std::vector(string):

vector(string), string, vector(int), vector(float) and bool

example:

 bool x =  (bool) myClass["dummy_boolean_type"]
 x = myClass["dummy_boolean_type"].as<bool>
 int y = (int) myClass["dummy_boolean_type"]

Can someone have an example what is the best way to achieve it ? (without a use in Boost)

Shaul
  • 27
  • 4
  • `myClass["dummy_boolean_type"]` that's not valid... – Borgleader Jul 06 '15 at 15:30
  • 1
    What would returning `string` or `bool` mean for the user? Like, when would the `bool` be true or false? – KABoissonneault Jul 06 '15 at 15:30
  • So basically you want to store different types in your map? – Chan Kha Vu Jul 06 '15 at 15:30
  • 1
    possible duplicate of [How to convert a number to string and vice versa in C++](http://stackoverflow.com/questions/5290089/how-to-convert-a-number-to-string-and-vice-versa-in-c) – Captain Obvlious Jul 06 '15 at 15:32
  • However it really unclear for few-times reading, but I've got the idea: author wants `std::vector` to be convertible to types: `bool`, `std::vector`, `std::vector`. Am I right, @Shaul ? – VP. Jul 06 '15 at 15:42
  • @VictorPolevoy - exactly. i want to add a member function (i assume it will be a template) so user can ask to have the value for specific key in different format than std::vector(string). – Shaul Jul 06 '15 at 18:28
  • @Shaul you might to use 2 techniques: `type erasure` or `user-defined conversions` with a wrapper for `std::vector`. I'd answer but unfortunately I dont know how to answer while the question is put on hold. – VP. Jul 06 '15 at 19:18
  • @Borgleader how to open this question back? – VP. Jul 06 '15 at 19:19

1 Answers1

1

You cannot provide your class with any member function or functions that will support the two constructions you want:

T x = myClassInstance["key"].as<T>  // (1)
T x =  (T) myClassInstance["key"]   // (2)

In the case of (1), the immediate reason is simply that the construction is not legal C++. So let's suppose it is replaced by:

T x = myClassInstance["key"].as<T>()  // (1a)

But this doesn't help, and the reason is the same for both (1a) and (2):

The expression myClassInstance["key"] will have to evaluate to some object e or reference to such that is returned by:

myClass::operator[](std::string const &);

This e is the vector<string> to which key maps in your map<string,vector<string> data member of myClass. It is not myClassInstance.

So what you are asking for are member functions of myClass that will support the constructions:

T x = e.as<T> // (1b)
T x =  (T) e  // (2b)

where e is an std::vector<std::string>.

Clearly, nothing you can do in myClass can address your requirement. In (1b) and (2b), myClass is nowhere to be seen.

Is there any template member function of std::vector<std::string>:

template<typename T>
T as() const;

that has the behaviour you want for (1b)?

Does std::vector<std::string> have any template member function:

template<typename T>
operator T() const;

that has the behaviour you want for (2b)?

No and No.

However...

You can implement an as template member function of myClass that supports conversions such as you mention. Its signature - of course - would be:

template<typename T>
T myClass::as(std::string const & key) const;

and you would invoke it like:

T x = myClassInstance.as<T>("key")  // (1c)

I'll sketch an implemention that assumes we're content with the conversions from std::vector<std::string> to:

  • std::string
  • std::vector<int>
  • bool

which will be enough to get you under way.

To convert an std::vector<std::string> vs to std::string I'll concatenate the elements of vs.

To convert an std::vector<std::string> vs to std::vector<int> I'll convert each element of vs from a decimal numeral, if I can, to the integer the numeral represents, and return a vector of those integers. Without prejudice to other policies I'll throw an std::invalid_argument exception when an element doesn't trim to a decimal numeral.

I am spoilt for choice as to what you might mean by converting an std::vector<std::string> to bool, so I will arbitrarily say that vs is true if I can convert it to a vector<int> of which any element is non-0 and is false if I can convert it to a vector<int> in which all elements are 0.

The sketch:

#include <type_traits>
#include <map>
#include <vector>
#include <string>
#include <algorithm>

namespace detail {

template<typename T>
struct convert_to
{
    static T from(std::vector<std::string> const & vs) {
        static constexpr bool always_false = !std::is_same<T,T>::value;
        static_assert(always_false,
            "Calling `convert_to<T>::from` for unimplemented `T`");
        return *(T*)nullptr;
    }
};

template<>
std::string convert_to<std::string>::from(std::vector<std::string> const & vs)
{
    std::string s;
    for (  auto const & e : vs ) {
        s += e;
    }
    return s;
}

template<>
std::vector<int> 
convert_to<std::vector<int>>::from(std::vector<std::string> const & vs)
{
    auto lamb = [](std::string const & s) {
        std::size_t lastoff = s.find_last_not_of(" \t\f\v\n\r");
        int i;
        try {
            std::size_t nlen;
            i = std::stoi(s,&nlen);
            if (nlen <= lastoff) {
                throw std::invalid_argument("");
            }
        }
        catch(std::invalid_argument const & e) {
            throw std::invalid_argument(
                "Cannot convert \"" + s + "\" to int");
        }
        return i;
    };
    std::vector<int> vi;
    std::transform(vs.begin(),vs.end(),std::back_inserter(vi),lamb);
    return vi;
}

template<>
bool convert_to<bool>::from(std::vector<std::string> const & vs)
{
    auto vi = convert_to<std::vector<int>>::from(vs);
    for (auto const & i : vi) {
        if (i) {
            return true;
        }
    }
    return false;

}

} // namespace detail


struct myClass // Your class
{
    // Whatever...

    std::vector<std::string> & operator[](std::string const & key) {
        return _map[key];
    }

    template<typename T>
    T as(std::string const & key) {
        return detail::convert_to<T>::from(_map[key]);
    }

    // Whatever...

private:
    std::map<std::string,std::vector<std::string>> _map;
};

The one take-away point here is the use of template<typename T> detail::struct convert_to, with its solitary static member function:

T from(std::vector<std::string> const & vs)

which in the default instantiation will provoke a static_assert failure reporting that no conversion to T from std::vector<std::string> has been defined.

Then, for each type U to which you want a conversion, you have just to write a specializing definition:

template<>
U convert_to<U>::from(std::vector<std::string> const & vs);

as you see fit, and the construction (1c) will use it as per:

template<typename T>
T myClass::as(std::string const & key) {
    return detail::convert_to<T>::from(_map[key]);
}

Here's an illustrative progam you can append to the sketch:

#include <iostream>

using namespace std;

template<typename T>
static void print_vec(std::vector<T> const & v)
{
    cout << "{ ";
    for (auto const & e : v) {
        cout << e << " ";
    }
    cout << "}\n";
}

static void print_vec(std::vector<std::string> const & v)
{
    cout << "{ ";
    for (auto const & e : v) {
        cout << '\"' << e << "\" ";
    }
    cout << "}\n";
}

int main()
{
    myClass f;
    f["int_vec"] = vector<string>{"0","1 "," 2"};
    cout << "f[\"int_vec\"] = "; print_vec(f["int_vec"]); 
    f["true_vec"] = vector<string>{"0"," 1 ","0"};
    cout << "f[\"true_vec\"] = "; print_vec(f["true_vec"]);
    f["false_vec"] = vector<string>{"0"," 0","0 "};
    cout << "f[\"false_vec\"] = "; print_vec(f["false_vec"]);
    f["not_int_vec0"] = vector<string>{"0","1","2",""};
    cout << "f[\"not_int_vec0\"] = "; print_vec(f["not_int_vec0"]);
    f["not_int_vec1"] = vector<string>{"0","@","2",};
    cout << "f[\"not_int_vec1\"] = "; print_vec(f["not_int_vec1"]);
    f["not_int_vec2"] = vector<string>{"0"," 1$","2",};
    cout << "f[\"not_int_vec2\"] = "; print_vec(f["not_int_vec2"]);
    cout << "f.as<string>(\"int_vec\") = \"" 
        << f.as<string>("int_vec") << '\"' << endl;
    cout << "f.as<string>(\"true_vec\") = \"" 
        << f.as<string>("true_vec") << '\"' << endl;
    cout << "f.as<string>(\"false_vec\") = \"" 
        << f.as<string>("false_vec") << '\"' << endl;
    cout << "f.as<string>(\"not_int_vec0\") = \"" 
        << f.as<string>("not_int_vec0") << '\"' << endl;
    cout << "f.as<string>(\"not_int_vec1\") = \""
        << f.as<string>("not_int_vec1") << '\"' << endl;
    cout << "f.as<string>(\"not_int_vec2\") = \""
        << f.as<string>("not_int_vec2") << '\"' << endl;
    vector<int> va = f.as<vector<int>>("int_vec");
    cout << "f.as<vector<int>>(\"int_vec\") = "; 
    print_vec(f.as<vector<int>>("int_vec"));
    cout << boolalpha << "f.as<bool>(\"true_vec\") = " 
        << f.as<bool>("true_vec") << endl;
    cout << boolalpha << "f.as<bool>(\"false_vec\") = " 
        << f.as<bool>("false_vec") << endl;
    try {
        cout << "f.as<vector<int>>(\"not_int_vec0\")...";
        auto b = f.as<vector<int>>("not_int_vec0");
        (void)b;
    }
    catch(std::invalid_argument const & e) {
        cout << e.what() << endl;
    }
    try {
        cout << "f.as<vector<int>>(\"not_int_vec1\")...";
        auto b = f.as<vector<int>>("not_int_vec1");
        (void)b;
    }
    catch(std::invalid_argument const & e) {
        cout << e.what() << endl;
    }
    try {
        cout << "f.as<vector<int>>(\"not_int_vec2\")...";
        auto b = f.as<vector<int>>("not_int_vec2");
        (void)b;
    }
    catch(std::invalid_argument const & e) {
        cout << e.what() << endl;
    }
    // char ch = f.as<char>("int_vec"); <- static_assert fails
    return 0;
}

It outputs:

f["int_vec"] = { "0" "1 " " 2" }
f["true_vec"] = { "0" " 1 " "0" }
f["false_vec"] = { "0" " 0" "0 " }
f["not_int_vec0"] = { "0" "1" "2" "" }
f["not_int_vec1"] = { "0" "@" "2" }
f["not_int_vec2"] = { "0" " 1$" "2" }
f.as<string>("int_vec") = "01  2"
f.as<string>("true_vec") = "0 1 0"
f.as<string>("false_vec") = "0 00 "
f.as<string>("not_int_vec0") = "012"
f.as<string>("not_int_vec1") = "0@2"
f.as<string>("not_int_vec2") = "0 1$2"
f.as<vector<int>>("int_vec") = { 0 1 2 }
f.as<bool>("true_vec") = true
f.as<bool>("false_vec") = false
f.as<vector<int>>("not_int_vec0")...Cannot convert "" to int
f.as<vector<int>>("not_int_vec1")...Cannot convert "@" to int
f.as<vector<int>>("not_int_vec2")...Cannot convert " 1$" to int

(gcc 5.1, clang 3.6, C++11)

Mike Kinghan
  • 55,740
  • 12
  • 153
  • 182