C++ 在一个向量中保存具有不同模板参数的类实例,但保留其属性
我想有一个程序,为我分析和管理命令行参数。正如您在C++ 在一个向量中保存具有不同模板参数的类实例,但保留其属性,c++,templates,sfinae,C++,Templates,Sfinae,我想有一个程序,为我分析和管理命令行参数。正如您在main-函数中所看到的,通过使用简单的命令,如Option(“number”、{“-n”、“--number”})可以指定选项值应该具有的类型(如本例中的int),每个选项都有一个唯一的标识符(如“number”),和多个字符串,此选项可以与一起引入。此外,许多选项应该封装在名为OptionSet的类中,这样可以简化对其选项的访问 但在我的实际代码中,我现在有几个问题: 我想在一个std::vector中存储具有不同模板参数的一个类的多个实例。
main
-函数中所看到的,通过使用简单的命令,如Option(“number”、{“-n”、“--number”})
可以指定选项值应该具有的类型(如本例中的int
),每个选项都有一个唯一的标识符(如“number”
),和多个字符串,此选项可以与一起引入。此外,许多选项应该封装在名为OptionSet
的类中,这样可以简化对其选项的访问
但在我的实际代码中,我现在有几个问题:
std::vector
中存储具有不同模板参数的一个类的多个实例。例如,在我的代码中,选项
应该存储在同一个向量中,就像选项
和选项
甚至可以将模板参数单独存储在另一个向量中
using
,std::enable\u if\u t
和std::is\u same
我创建了一个名为OptionHasValue
的类型。如果模板参数Invert
为false且T
为void
,OptionHasValue
具有无效类型,否则它具有模板参数U
指定的类型类
OptionValue
使用OptionHasValue
和一点SFINAE魔法来决定它是否应该具有支持值存储所需的方法。也就是说,OptionValue
的第一个版本将OptionHasValue
作为其第二个模板参数,因此如果T
为void
,则它将无效(并被编译器删除)。另一个版本的OptionValue
具有相反的行为,因为它的第二个模板参数是OptionHasValue
,而true
反转了OptionHasValue
的行为类
选项
本身继承自选项值
,因此如果您创建一个像选项
这样的选项,它就不支持值(也就是说,它缺少像setValue
、setValueFromString
和getValue
这样的函数)。另一方面,如果您创建一个类似于option
的选项,则生成的类实例具有所有这些特性。现在的问题是,(例如)
OptionSet::process()
同时访问Option::hasValue
和Option::setValueFromString
,但后者仅在Option::hasValue
为true时声明(并且该选项对应的模板参数不是void
)。但是因为这里的Option::setValueFromString
没有包装在某种模板中,编译器也会抱怨main
-函数中,我使用函数optionSet.getOptionValue(std::string)
。此函数应返回选项的值(在调用process()
后设置)。现在困难的是返回类型取决于findOptionByIdentifier
的返回值,该函数循环遍历所有可用选项并返回带有所需标识符的选项。例如,如果
identifier
将是“number”
(如本问题开头的选项的示例),则findOptionByIdentifier
的返回类型将是选项
,因为唯一具有标识符的选项是“number”
是将int
作为其第一个模板参数的模板,它最终将导致getOptionValue
具有返回类型int
您可以在main
-函数的最后几行的注释中看到预期的行为
#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;
}
#包括
#包括
#包括
#包括
#包括
#包括
#包括
#包括
模板
使用OptionHasValue=std::启用//只有在类型为“void”的情况下(当“Invert”为false时)T不是时,才能使此模板替换成功
模板
类别期权价值;
模板
在此处使用SFINAE(“替换失败不是错误”)初始化OptionValue//
{
受保护的:
T值;
公众:
无效设置值(T新值)
{
值=新值;
}
void setValueFromString(std::string newValueStr)
{
setValue(boost::词法转换(newValueStr));
}
T getValue()
{
返回值;
}
布尔值()
{
return true;//如果编译器采用该类变量,则将从中继承的“Option”将有一个值
}
};
模板
class OptionValue//相反的条件(“true”将其反转)
{
//选项值已禁用,但要检查派生类中是否有可用的值,请为此添加一个函数(或者我是否应该?)
公众:
布尔值()
{
返回false;
}
};
模板
类选项:公共选项值
{
私人:
字符串标识符;
std::载体变异体;
公众:
选项(std::string newIdentifier,std::vector newVariants)
{
标识符=新标识符;
变体=新变体;
}
布尔变量(标准::字符串v)
{
return(std::find(variants.begin(),variants.end(),v)!=variants.end());
}
std::string getIdentifier()
{
返回标识符;
}
};
O类