Home > Enterprise >  Templated sub-class with `std::string`-to-`T` conversion in virtual function
Templated sub-class with `std::string`-to-`T` conversion in virtual function

Time:09-29

I have a sub-class that has to implement a virtual function so that callers can interface with it without knowing its concrete type. But I'd like to template this sub-class to work with different types. Basically, this:

class Base
{
public:
    virtual void insert(const std::string & val, const std::string & type_str) = 0;
};

template<typename T>
class C : public Base
{
public:
    virtual void insert(const std::string & val, const std::string & type_str) final
    {
        // Something that tries to convert val to type T and inserts into container;
        // Would throw if conversion fails

        // e.g.:
        if (type_str == "int")
            container.insert(std::stoi(val));
        else if (type_str == "str")
            container.insert(val); // Error: No matching function call to `insert` [...]
    }

private:
    std::set<T> container;
};

I understand the problem: any given specialization of C won't have a container that accepts types other than T; it's only a runtime assurance that nobody calls C::insert() with the wrong type.

Is there any way around this? I'd like to keep the same interface for this sub-class as I have with others that inherit from Base, which requires accepting a string and doing the conversion internally.

More than happy to have a C 20 solution (had some failed attempts using concepts to solve this...). And if it matters, I'm using gcc.

CodePudding user response:

you can use if constexpr (c 17)

class Base
{
public:
    virtual void insert(const std::string & val, const std::string & type_str) = 0;
};

template<typename T>
class C : public Base
{
public:
    virtual void insert(const std::string & val, const std::string & type_str) final
    {
        if constexpr(std::is_same_v<T,int>){
            assert(type_str == "int"); // or you can just drop this, depend on what you want
            container.insert(std::stoi(val));
        }
        else if constexpr(std::is_same_v<T,std::string>){
            assert(type_str == "str");
            container.insert(val); 
        }
        else{
            static_assert(!std::is_same_v<T,T>,"not supported conversion");
        }
    }

private:
    std::set<T> container;
};

Or you can use template specialization

CodePudding user response:

For a C 20 concepts solution, you could factor the insert logic out into helper functions and add requires clauses to make sure T is convertible to the type given by type_str at runtime. For example:

template <typename T>
T parse(const std::string& val, const std::string& type_str)
requires std::convertible_to<T, int>
{
    if (type_str != "int") {
        throw std::runtime_error("can't convert to int");
    }
    
    return std::stoi(val);
}

template <typename T>
T parse(const std::string& val, const std::string& type_str)
requires std::convertible_to<T, std::string>
{
    if (type_str != "str") {
        throw std::runtime_error("can't convert to str");
    }
    
    return val;
}

template<typename T>
class C : public Base
{
public:
    virtual void insert(const std::string & val, const std::string & type_str) final
    {
        container.insert(parse<T>(val, type_str));
    }

private:
    std::set<T> container;
};

Demo

  • Related