c++ - 在一个 vector 中保存具有不同模板参数的类实例,但保留它们的属性

标签 c++ templates sfinae

我想要一个程序来为我解析和管理命令行参数。正如您在 main 中看到的那样-function,通过使用像Option<int>("number", { "-n", "--number" })这样的简单命令您可以指定选项值应具有的类型(如本例中的 int),每个选项的唯一标识符(如 "number"),以及可以引入此选项的多个字符串。此外,许多选项应该包含在一个名为 OptionSet 的类中。 ,这简化了对其选项的访问。

但在我的实际代码中,我现在遇到了几个问题:

  1. 我想在一个 std::vector 中存储具有不同模板参数的一个类的多个实例.例如,在我的代码中,Option<int>应存储在与 Option<std::string> 相同的 vector 中和 Option<double> .
    也许甚至可以将模板参数单独存储在另一个 vector 中?
  2. 通过使用 using , std::enable_if_tstd::is_same我创建了一个名为 OptionHasValue 的类型.如果模板参数Invert是假的,Tvoid , OptionHasValue具有无效类型,否则它具有由模板参数指定的类型 U .
    类(class)OptionValue使用 OptionHasValue以及一些 SFINAE 魔法来决定它是否应该具有支持值存储所需的方法。即第一个版本OptionValueOptionHasValue<T>作为它的第二个模板参数,所以如果T,它就会变得无效(并被编译器删除)是void . OptionValue的其他版本具有相反的行为,因为它的第二个模板参数是 OptionHasValue<T, true>true反转 OptionHasValue 的行为.
    类(class)Option本身继承自 OptionValue , 所以如果你创建一个像 Option<void> 这样的选项,它不支持值(也就是说,它缺少像 setValuesetValueFromStringgetValue 这样的功能)。另一方面,如果您创建类似 Option<int> 的选项,生成的类实例具有所有这些功能。
    现在的问题是,(例如)OptionSet::process()访问 Option::hasValueOption::setValueFromString , 但后者仅在 Option::hasValue 时声明为真(选项对应的模板参数不是 void )。但是因为Option::setValueFromString这里没有包裹在某种模板中,编译器也会提示。
  3. 在我的 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/

相关文章:

c# - 如何检测 Windows 开始菜单/开始屏幕何时打开?

c++ - 如何区分 unsigned int 和 uint32_t

c++ - SFINAE 检查失败

templates - SFINAE 上缺少一些内容(虚拟模板参数形式)

c++ - 使用 SFINAE 与 GCC 检测方法

c++ - C - 使用 PThreads 时更快地锁定整数

css - django 模板 forloop.counter 问题

c++ - 检查容器模板类中的迭代器类型

C++ 表达式 SFINAE 和 ostream 操纵器

c++ - 如何优化此数组抽取/下采样程序的内存访问模式/缓存未命中?