简体   繁体   English

将具有不同模板参数的类实例保存在一个矢量中,但保留其属性

[英]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: 但是在我的实际代码中,我现在遇到几个问题:

  1. I want to store multiple instances of one class with different template parameters within one 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>相同的向量中。
    Maybe it's even possible to store the template parameters separately in another vector? 甚至有可能将模板参数分别存储在另一个向量中?
  2. By using using , std::enable_if_t and std::is_same I created a type called OptionHasValue . 通过usingstd::enable_if_tstd::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且Tvoid ,则OptionHasValue的类型无效,否则,其类型由模板参数U指定。
    The class 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>作为其第二个模板参数,因此如果Tvoid ,它将变为无效(并由编译器删除)。 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的行为。
    The class 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>的选项,则它不支持值(也就是说,它缺少应有的诸如setValuesetValueFromStringgetValue类的功能)。 On the other hand, if you create an option like Option<int> , the resulting class instance has all of these features. 另一方面,如果创建选项Option<int> ,则所得的类实例具有所有这些功能。
    The problem now is, that (for example) 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::hasValueOption::setValueFromString ,但仅当Option::hasValue为true时才声明后者(以及该选项的相应模板参数)不是void )。 But because Option::setValueFromString is not wrapped in some kind of template here, the compiler also complains. 但是由于Option::setValueFromString没有包装在某种模板中,因此编译器也会抱怨。
  3. In my 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的返回值,该函数循环遍历所有可用选项并返回带有所需标识符的选项。
    For example, if 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
    You can see the expected behavior in comments in some of the last lines of the 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM