我想要一个程序来为我解析和管理命令行参数。正如您在 main
中看到的那样-function,通过使用像Option<int>("number", { "-n", "--number" })
这样的简单命令您可以指定选项值应具有的类型(如本例中的 int
),每个选项的唯一标识符(如 "number"
),以及可以引入此选项的多个字符串。此外,许多选项应该包含在一个名为 OptionSet
的类中。 ,这简化了对其选项的访问。
但在我的实际代码中,我现在遇到了几个问题:
- 我想在一个
std::vector
中存储具有不同模板参数的一个类的多个实例.例如,在我的代码中,Option<int>
应存储在与Option<std::string>
相同的 vector 中和Option<double>
.
也许甚至可以将模板参数单独存储在另一个 vector 中? - 通过使用
using
,std::enable_if_t
和std::is_same
我创建了一个名为OptionHasValue
的类型.如果模板参数Invert
是假的,T
是void
,OptionHasValue
具有无效类型,否则它具有由模板参数指定的类型U
.
类(class)OptionValue
使用OptionHasValue
以及一些 SFINAE 魔法来决定它是否应该具有支持值存储所需的方法。即第一个版本OptionValue
有OptionHasValue<T>
作为它的第二个模板参数,所以如果T
,它就会变得无效(并被编译器删除)是void
.OptionValue
的其他版本具有相反的行为,因为它的第二个模板参数是OptionHasValue<T, true>
和true
反转OptionHasValue
的行为.
类(class)Option
本身继承自OptionValue
, 所以如果你创建一个像Option<void>
这样的选项,它不支持值(也就是说,它缺少像setValue
,setValueFromString
和getValue
这样的功能)。另一方面,如果您创建类似Option<int>
的选项,生成的类实例具有所有这些功能。
现在的问题是,(例如)OptionSet::process()
访问Option::hasValue
和Option::setValueFromString
, 但后者仅在Option::hasValue
时声明为真(选项对应的模板参数不是void
)。但是因为Option::setValueFromString
这里没有包裹在某种模板中,编译器也会提示。 - 在我的
main
-function 我使用函数optionSet.getOptionValue(std::string)
.此函数应返回选项的值(在调用process()
后设置)。现在难的是返回类型取决于findOptionByIdentifier
的返回值,一个循环遍历所有可用选项并返回具有所需标识符的选项的函数。
例如,如果identifier
将是"number"
(如本问题开头的Option
示例),findOptionByIdentifier
的返回类型将是Option<int>
,因为唯一具有标识符"number"
的选项是一个有int
的作为它的第一个模板参数,最终将导致getOptionValue
具有返回类型int
.
您可以在main
的最后几行的评论中看到预期的行为。 -功能。
那么,我必须在以下代码中更改什么才能修复所有这些问题(并使其编译)?我使用的是 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 want to store multiple instances of one class with different template parameters
没有这样的东西。具有不同模板参数的模板是不同的类。但是,您似乎通过 boost::any
成功地解决了它。您还可以使用另一种类型删除技术——例如,为所有选项设置一个非模板父级,或者切换到非类型删除boost::variant
,因为您似乎只有有限数量的可能选项类型。
By using using, std::enable_if_t and std::is_same I created a type called OptionHasValue...
首先,我不会在这个例子中使用 SFINAE。简单的部分特化就足够了。至于 opt.setValueFromString(argvVec[i]);
只需在 void 选项类中创建一个 NOOP 函数即可。
至于最后一个问题,只需使用一个模板函数,它接收对返回类型的引用,而不是返回它。
关于c++ - 在一个 vector 中保存具有不同模板参数的类实例,但保留它们的属性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35045208/