[英]Saving class instances with different template parameters inside one vector but keep their properties
I would like to have a program that parses and manages command-line parameters for me. 我想拥有一个为我解析和管理命令行参数的程序。 As you can see in the
main
-function, by using simple commands like Option<int>("number", { "-n", "--number" })
you can specify the type the option's value should have (like int
in this case), an unique identifier for each option (like "number"
), and multiple strings this option can be introduced with. 如您在
main
功能中所看到的,通过使用简单的命令,例如Option<int>("number", { "-n", "--number" })
您可以指定选项值应具有的类型(例如int
(在这种情况下),每个选项的唯一标识符(例如"number"
)以及可以引入此选项的多个字符串。 Also, many options should be wrapped in a class called OptionSet
, which simplifies access to its options. 同样,许多选项应该包装在一个名为
OptionSet
的类中,该类可以简化对其选项的访问。
But in my actual code, I am having several problems right now: 但是在我的实际代码中,我现在遇到几个问题:
std::vector
. std::vector
存储具有不同模板参数的一个类的多个实例。 For example, in my code, Option<int>
should be stored in the same vector like Option<std::string>
and Option<double>
. Option<int>
应该存储在与Option<std::string>
和Option<double>
相同的向量中。 using
, std::enable_if_t
and std::is_same
I created a type called OptionHasValue
. using
, std::enable_if_t
和std::is_same
我创建了一个名为类型OptionHasValue
。 If the template parameter Invert
is false and T
is void
, OptionHasValue
has an invalid type, otherwise it has the type specified by the template parameter U
. Invert
为false且T
为void
,则OptionHasValue
的类型无效,否则,其类型由模板参数U
指定。 OptionValue
uses OptionHasValue
and a bit of SFINAE magic to decide if it should have the needed methods for supporting the storage of values or not. OptionValue
类使用OptionHasValue
和一些SFINAE魔术来确定它是否应该具有支持值存储的所需方法。 That is, the first version of OptionValue
has OptionHasValue<T>
as its second template parameter, so it becomes invalid (and removed by the compiler) if T
is void
. OptionValue
的第一个版本将OptionHasValue<T>
作为其第二个模板参数,因此如果T
为void
,它将变为无效(并由编译器删除)。 The other version of OptionValue
has the opposite behavior, because its second template parameter is OptionHasValue<T, true>
and the true
inverts the behavior of OptionHasValue
. OptionValue
具有相反的行为,因为它的第二个模板参数是OptionHasValue<T, true>
,而true
反转OptionHasValue
的行为。 Option
itself inherits from OptionValue
, so if you create an option like Option<void>
, it does not have support for values (that is, it lacks functions like setValue
, setValueFromString
and getValue
as it should). Option
类本身是从OptionValue
继承的,因此,如果创建如Option<void>
的选项,则它不支持值(也就是说,它缺少应有的诸如setValue
, setValueFromString
和getValue
类的功能)。 On the other hand, if you create an option like Option<int>
, the resulting class instance has all of these features. Option<int>
,则所得的类实例具有所有这些功能。 OptionSet::process()
accesses both Option::hasValue
and Option::setValueFromString
, but the latter is only declared if Option::hasValue
is true (and the corresponding template parameter for the option is not void
). OptionSet::process()
访问Option::hasValue
和Option::setValueFromString
,但仅当Option::hasValue
为true时才声明后者(以及该选项的相应模板参数)不是void
)。 But because Option::setValueFromString
is not wrapped in some kind of template here, the compiler also complains. Option::setValueFromString
没有包装在某种模板中,因此编译器也会抱怨。 main
-function I use the function optionSet.getOptionValue(std::string)
. main
函数中,我使用函数optionSet.getOptionValue(std::string)
。 This function should return the value of an option (after it has been set after process()
has been called). process()
之后设置它的值之后)。 The difficult thing now is that the return type depends on the return value of findOptionByIdentifier
, a function which loops through all available options and returns the option with the wanted identifier. findOptionByIdentifier
的返回值,该函数循环遍历所有可用选项并返回带有所需标识符的选项。 identifier
would be "number"
(as in the example for an Option
at the beginning of this question), the return type of findOptionByIdentifier
would be Option<int>
, because the only option having the identifier "number"
is the one which has int
as its first template parameter, which would finally result in getOptionValue
having the return type int
. identifier
为"number"
(例如在此问题开头的Option
的示例中),则findOptionByIdentifier
的返回类型为Option<int>
,因为唯一具有标识符"number"
选项是一个以int
为第一个模板参数的参数,最终将导致getOptionValue
的返回类型为int
。 main
-function. main
函数的最后几行的注释中看到预期的行为。 So, what do I have to change in the following code to fix all these things (and to make it compile)? 因此,我必须在以下代码中进行哪些更改才能修复所有这些问题(并使之可编译)? I am using g++ 5.2.0 (mingw-w64), so I may use any feature of C++11 and C++14.
我正在使用g ++ 5.2.0(mingw-w64),因此我可以使用C ++ 11和C ++ 14的任何功能。
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <stdexcept>
#include <type_traits>
#include <boost/lexical_cast.hpp>
#include <boost/any.hpp>
template<typename T, bool Invert = false, typename U = void>
using OptionHasValue = std::enable_if_t<(!std::is_same<T, void>::value) ^ Invert, U>; //only make this template substitution successful, if (when 'Invert' is false) T is not if type 'void'
template<typename T, typename Enable = void>
class OptionValue;
template<typename T>
class OptionValue<T, OptionHasValue<T>> //using SFINAE ("substitution failure is not an error") here
{
protected:
T value;
public:
void setValue(T newValue)
{
value = newValue;
}
void setValueFromString(std::string newValueStr)
{
setValue(boost::lexical_cast<T>(newValueStr));
}
T getValue()
{
return value;
}
bool hasValue()
{
return true; //if this class variant is taken by the compiler, the 'Option' that will inherit from it will have a value
}
};
template<typename T>
class OptionValue<T, OptionHasValue<T, true>> //the opposite condition (the 'true' inverts it)
{
//option value is disabled, but to check if a value is available in the derived class, add a function for that (or should I not?)
public:
bool hasValue()
{
return false;
}
};
template<typename T>
class Option : public OptionValue<T>
{
private:
std::string identifier;
std::vector<std::string> variants;
public:
Option(std::string newIdentifier, std::vector<std::string> newVariants)
{
identifier = newIdentifier;
variants = newVariants;
}
bool hasVariant(std::string v)
{
return (std::find(variants.begin(), variants.end(), v) != variants.end());
}
std::string getIdentifier()
{
return identifier;
}
};
class OptionSet
{
private:
std::vector<boost::any> options; //boost::any can't be the right way to do this, or is it?
std::vector<std::string> argvVec;
template<typename T>
Option<T>& findOptionByIdentifier(std::string identifier)
{
for(auto& o : options)
if(o.getIdentifier() == identifier) //of course this doesn't compile, because 'o' will always be of type 'boost::any', but what should I do instead?
return o;
throw std::runtime_error("error: unable to find option by identifier \"" + identifier + "\"\n");
}
template<typename T>
Option<T>& findOptionByVariant(std::string variant)
{
for(auto& o : options)
if(o.hasVariant(variant)) //probably almost the same compile error like in 'findOptionByIdentifier'
return o;
throw std::runtime_error("error: unable to find option by variant \"" + variant + "\"\n");
}
public:
template<typename t>
void add(Option<T> opt)
{
options.push_back(opt); //is this the right way to add instances of classes with different template parameters to a vector?
}
void setArgvVec(std::vector<std::string> newArgvVec)
{
argvVec = newArgvVec;
}
void process()
{
for(size_t i=0; i<argvVec.size(); i++)
{
Option<T>& opt = findOptionByVariant(argvVec[i]); //of course this doesn't compile either, but what should I do instead?
if(opt.hasValue())
{
if(i == argvVec.size()-1)
throw std::runtime_error("error: no value given for option \"" + argvVec[i] + "\"\n");
opt.setValueFromString(argvVec[i]); //boost::bad_lexical_cast should be caught here, but that's not important right now
i++;
}
}
}
template<typename T>
T getOptionValue(std::string identifier)
{
Option<T>& opt = findOptionByIdentifier(identifier); //a bit like the call to 'findOptionByVariant' in 'process()'. also, this variable does not have to be a reference
if(!opt.hasValue())
throw std::runtime_error("error: option with identifier \"" + identifier + "\" has no value\n");
return opt.getValue();
}
};
int main()
{
OptionSet optionSet;
//it's not guaranteed that OptionSet::add will always receive a rvalue, I just do it here for shorter code/simplicity
optionSet.add(Option<void>("help", { "-?", "--help" })); //if it's a void-option, the 'Option' does not have a value, if the template parameter is anything else, it has one (like below)
optionSet.add(Option<std::string>("message", { "-m", "--message" }));
optionSet.add(Option<int>("number", { "-n", "--number" }));
optionSet.add(Option<double>("pi", { "-p", "--pi" }));
optionSet.setArgvVec({ "--help", "-m", "hello", "--number", "100", "--pi", "3.14" });
optionSet.process();
std::string message = optionSet.getOptionValue("message");
int number = optionSet.getOptionValue("number");
double pi = optionSet.getOptionValue("pi");
std::cout << "Message: " << message << "\n"; //should output 'hello'
std::cout << "Number: " << number << "\n"; //should output '100'
std::cout << "Pi: " << pi << "\n"; //should output something like '3.140000'
return 0;
}
I am not sure I fully understood the question, but I will try to answer it. 我不确定我是否完全理解这个问题,但是我会尽力回答。
I want to store multiple instances of one class with different template parameters
我想使用不同的模板参数存储一个类的多个实例
There is no such thing. 哪有这回事。 A template with different template paramter is a different class.
具有不同模板参数的模板是不同的类。 However, you seem to be solving it successfully through
boost::any
. 但是,您似乎可以通过
boost::any
成功解决它。 You could also use another type-erasure technique - for example, have a non-template parent to all your options, or switch to non-type-erasure boost::variant
, as it seems like you only have a limited number of possible option types. 您还可以使用另一种类型擦除技术-例如,对所有选项使用非模板父级,或切换到非类型擦除
boost::variant
,因为似乎只有有限数量的可能选项类型。
By using using, std::enable_if_t and std::is_same I created a type called OptionHasValue...
通过使用std :: enable_if_t和std :: is_same,我创建了一个名为OptionHasValue的类型。
First of all, I would not use SFINAE in this example. 首先,在此示例中,我将不使用SFINAE。 Simple partial specialization will suffice.
简单的局部专业化就足够了。 As for
opt.setValueFromString(argvVec[i]);
至于
opt.setValueFromString(argvVec[i]);
just create a NOOP function in void option class. 只需在void选项类中创建NOOP函数。
As for the last question, just use a templated function which receives a reference to the return type, instead of returning it. 对于最后一个问题,只需使用接收返回类型引用的模板化函数即可,而不是返回它。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.