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;
};