【问题标题】:Using templates in std::conditional to determine function argument types在 std::conditional 中使用模板来确定函数参数类型
【发布时间】:2020-02-27 01:43:56
【问题描述】:

我希望我所有的数据保存和加载都通过相同的功能来减少出现错误的机会。为此,我使用了很多模板(以及很多函数重载)。它起作用了,我的代码现在更干净了,但我无法使用const 进行保存(因为它通过与加载器相同的功能,其中数据保持非常量)。

我想正确使用const,所以这里尝试让一个简单的版本工作,其中数据(在本例中为std::vector)对于std::ifstream和@987654326来说是非常量的@否则:

#include <iostream>
#include <fstream>
#include <vector>

template <class Foo>
void Overload(const Foo & foo)
{
    std::cout << "went to const" << std::endl;
}

template <class Foo>
void Overload(Foo & foo)
{
    std::cout << "went to non-const" << std::endl;
}

template <class StreamType, typename... Arguments>
void ReadOrWrite (

    /* for 1st argument */ StreamType & filestream,

    /* type for 2nd argument */ typename std::conditional< 
        /* if */    std::is_same<StreamType, std::ifstream>::value,
            /* then */  std::vector<Arguments...>,
            /* else */  const std::vector <Arguments...>
            >::type

        /*2nd argument name */ & vector
        )
{
    Overload(vector);
}

int main ()
{
    std::ofstream output_filestream;
    std::ifstream intput_filestream;

    std::vector<int> vector;

    ReadOrWrite(output_filestream, vector);
    ReadOrWrite(intput_filestream, vector);

    return 0;
}

我知道如果我编辑对此的函数调用,它将正确编译/运行:

ReadOrWrite<std::ofstream, int>(output_filestream, vector);
ReadOrWrite<std::ifstream, int>(intput_filestream, vector);

但我不希望函数的用户在函数调用期间需要列出类型。

有没有一种干净的方法来做我建议的事情?

编辑

我的动机的合法性似乎存在疑问。

我没有彻底解释我的动机,因为它不是太简单(也不是太复杂),我尊重读者的时间。

我给出的示例是我无法解决的最简单的部分——“重载”函数只是为了看看它是否有效。

但是看来我的解释不够引起了混乱,所以我会解释一下:

我制作了一个小型库来处理一般情况下的数据保存和加载。它通过使用以下接口成功地允许用户的类具有简单的保存/加载方法:

class SomeClass
{
public:

    template <class StreamType>
    void SaveOrLoad(StreamType & filestream)
    {
        saveload::SaveToOrLoadFromFile(filestream,

            data_1_,
            data_2_,
            /* ..., */
            data_n_,
        );
    }

    void SaveToFile (const std::string & filename)
    {
        std::ofstream output_filestream(filename, std::ios::binary);

        // file handling

        SaveOrLoad(output_filestream);
    }

    void LoadFromFile (const std::string & filename)
    {
        std::ifstream input_filestream(ptf::problem_input_file, std::ios::binary);

        // file handling

        SaveOrLoad(input_filestream);
    }
};

该库处理所有基本数据类型、STL 容器和任何其他使用正确SaveOrLoad(StreamType &amp;) 接口的容器,包括所有容器的保存和调整大小。该库已强制所有保存和加载都通过相同的确定性函数,因此完全消除了涉及保存/加载不匹配的错误的可能性(除非用户滥用库的简单界面)。

我的图书馆遇到的问题 - 以及我提出问题的原因 - 是理论上的问题,因为我目前不需要它:SaveToFile 方法应该可以是 const

【问题讨论】:

  • 就目前而言,std::conditional 中的内容是非推断上下文,这就是为什么不会为您推断Arguments 的原因。我将在两者之间添加另一个级别:仅将T 作为第二个参数的函数,并且根据T 是否为const,调用单独的ReadOrWrite 函数(嗯,ReadWrite)。在 C++11 中,这比我们在较新版本中使用的 constexpr 要麻烦一些,但可行。我还看到了“我通过同一个函数进行读写”的大危险信号——这几乎肯定违反了单一责任原则......
  • "...以减少出现错误的机会。为此,我使用了很多模板"。这让我发笑。我认为你实际上是在创造一个上帝功能:一个可以做任何事情的功能。 That is considered an anti pattern
  • 我个人会编写单独的读写函数。他们做不同的事情,所以他们应该分开。这让您可以轻松地将const 应用到 write 函数中。
  • 我建议您从标准库及其处理容器的方式中汲取灵感:改为传递迭代器对。这通常会简化事情,并使您的功能更灵活地启动(因为它们可以处理其他容器和部分范围)。
  • 明确地说,我在一个数据库上使用了这个,其中嵌套数据类型的大型问题实例必须以与加载时完全相同的方式保存,否则您最终会得到非常严重错误加载,经常导致崩溃。每个类类型中的数据列表都很长,将加载和保存分开是一个非常糟糕的设计。

标签: c++ oop c++11 vector constants


【解决方案1】:

最好的建议是提供两个独立的函数,因为读取和写入是两个不同的操作,无论您发送什么类型。例如,对于输入和输出,某人可能是 fstream。仅仅通过类型系统,您无法知道意图。读或写的决定通常是一个意图,这些很少嵌入到类型系统中。

由于保存和加载是不同的操作,所以应该是不同的函数(可能在它们之间共享代码)


如果你真的想要一个既能做到又能在类型之间切换的函数,那么我建议限制输入或输出的函数:

// output streams
template <class StreamType, typename... Arguments,
    typename std::enable_if<std::is_base_of<std::ostream, StreamType>::value, int>::type = 0
>
void ReadOrWrite (
    StreamType & filestream,
    std::vector<Arguments...> const& vector
) {
    Overload(vector);
}

// input streams
template <class StreamType, typename... Arguments,
    typename std::enable_if<std::is_base_of<std::istream, StreamType>::value, int>::type = 0
>
void ReadOrWrite (
    StreamType& inputstream,
    std::vector<Arguments...>& vector
) {
    Overload(vector);
}

由于第二个比第一个更专业,只要流是std::istream 并且向量是可变的,就会采用它。否则,将采用第一个。

Live example

【讨论】:

  • 可能想要使用std::istream&amp; inputstream 而不是std::ifstream&amp; filestream
  • @NathanOliver-ReinstateMonica 很好地发现了!
  • 谢谢。不幸的是,现在我看多了你还需要更多的重载,或者限制模板,因为如果传递了 const istream 以外的东西,它将是完全匹配的。
  • 明确地说,我在一个数据库上使用了这个,其中嵌套数据类型的大型问题实例必须以与加载时完全相同的方式保存,否则您最终会得到非常严重错误加载,经常导致崩溃。每个类类型中的数据列表都很长,将加载和保存分开是一个非常糟糕的设计。
  • @ElliottSmith 收集元数据是另一个操作。保存和加载都应该使用相同的元数据,因此加载和保存都使用相同的元数据。
【解决方案2】:

另一个重载解决方案可以转换 ReadOrWrite(),几乎就像您在问题中所写的那样,在辅助函数中

template <typename ... Args, typename ST>
void ReadOrWrite_helper (ST &, typename std::conditional<
                                  std::is_same<ST, std::ifstream>::value,
                                  std::vector<Args...>,
                                  std::vector<Args...> const>::type vec)
 { Overload(vec); }

添加一对重载的 ReadOrWrite() 函数来选择选择 Args... 并显式调用帮助函数

template <typename ... Ts>
void ReadOrWrite (std::ifstream & is, std::vector<Ts...> & vec)
 { ReadOrWrite_helper<Ts...>(is, vec); }

template <typename ... Ts>
void ReadOrWrite (std::ofstream & is, std::vector<Ts...> const & vec)
 { ReadOrWrite_helper<Ts...>(is, vec); }

请注意,鉴于Args... 类型处于非推导上下文中,因此需要说明,我已将它们放在ReadOnWrite_helper() 模板参数声明中,之前 ST;所以也不需要显式ST

还请注意,如果您不需要知道 ReadOrWrite_helper() 中的 Args... 类型,一切都会变得更简单

template <typename V, typename ST>
void ReadOrWrite_helper (ST &, V & vec)
 { Overload(vec); }

template <typename V>
void ReadOrWrite (std::ifstream & is, V & vec)
 { ReadOrWrite_helper(is, vec); }

template <typename V>
void ReadOrWrite (std::ofstream & is, V const & vec)
 { ReadOrWrite_helper(is, vec); }

也消除了解释V类型的需要。

【讨论】:

  • 我很抱歉,我应该以不同的方式问我的问题。它确实需要在一个函数中,因为此函数用于调用许多其他 ReadOrWrite 函数,每次到达较低级别的数据(在本例中为向量的元素),然后最终到达算术数据类型两个重载函数,分为 std::ifstream / non-const 和 std::ofstream / const。投票给一个好的答案。 '可能会发布一个新问题,更好地解释问题所在。
【解决方案3】:

首先,抱歉,我对我的问题的解释很糟糕,我会做得更好。

以防万一有人碰巧读到这篇文章并遇到类似的问题,我通过引入两个 wrapper 函数来解决它,其目的是显式声明模板参数:

template <class DataType>
void ParseData (std::ofstream & output_filestream, const DataType & data)
{
    ReadOrWrite<std::ofstream, DataType> (output_filestream, data);
}

template <class DataType>
void ParseData (std::ifstream & input_filestream, DataType & data)
{
    ReadOrWrite<std::ifstream, DataType> (input_filestream, data);
}

重要的是这个解决方案是可扩展的:为了处理每种新的数据类型,我只需要编写一个ReadOrWrite 函数,在函数调用中没有不必要的模板参数。

ParseData 函数如何融入解决方案:

#include <iostream>
#include <fstream>
#include <vector>


// lots of useful typetraits:
#include <type_traits>

template <class S, class T = void>
struct is_vector : std::false_type {};

template <class S, class T>
struct is_vector <std::vector<S,T>> : std::true_type {};

template <typename Datatype>
inline constexpr bool is_vector_v = is_vector<Datatype>::value;

// Kinda similar format to my serialization library:

template <class Foo>
void Overload(const Foo & foo)
{
    std::cout << "went to const" << std::endl;
}

template <class Foo>
void Overload(Foo & foo)
{
    std::cout << "went to non-const" << std::endl;
}

// Special type trait specific to this library
// (within an anonymous namespace)

template <typename, typename Data>
struct vet_data_constness
     : std::add_const<Data> {};
    
template <typename Data>
struct vet_data_constness <std::ifstream, Data>
  : std::remove_const<Data> {};

template <typename StreamType, typename Data>
using vet_data_constness_t
        = typename vet_data_constness<StreamType,Data>::type;


template <class StreamType, typename DataType>
std::enable_if_t<is_vector_v<DataType>>
ReadOrWrite (StreamType & filestream,
             vet_data_constness_t<StreamType, DataType> & vector)
{
    Overload(vector);
}

// These functions are simply for routing back to the ReadOrWrite
// funtions with explicit calls

template <class DataType>
void ParseData (std::ofstream & output_filestream, const DataType & data)
{
    ReadOrWrite<std::ofstream, DataType> (output_filestream, data);
}

template <class DataType>
void ParseData (std::ifstream & input_filestream, DataType & data)
{
    ReadOrWrite<std::ifstream, DataType> (input_filestream, data);
}

int main ()
{
    std::ofstream output_filestream;
    std::ifstream intput_filestream;

    std::vector<int> vector;

    ParseData(output_filestream, vector);
    ParseData(intput_filestream, vector);

    return 0;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-30
    • 2020-12-04
    • 1970-01-01
    • 1970-01-01
    • 2018-04-06
    • 1970-01-01
    相关资源
    最近更新 更多