为什么函数递归似乎让条件变得疯狂?

问题描述 投票:0回答:1

首先:

    下面代码中的
  1. test_container
    是一个仅用于“调试”目的的函数。对我的工作项目没有真正的用处。

  2. 真正的函数称为

    foreach
    (参见本问题的最后一个代码)

  3. foreach
    是一个返回容器的 std::string 表示形式的函数(在此修复之后,我将使其也适用于 int 数组)

  4. 甚至这里的

    foreach
    表示也被最小化,以便更好地理解:它在这里看起来像
    test_container
    ,但有一些差异。


所以,我有这个代码:

#include <iostream>
#include <map>
#include <sstream>
#include <type_traits>
#include <vector>

template <typename T>
struct has_template_argument {
    static const bool value = false;
};

// Specialization of a template for types with template arguments
template <template <typename...> class Container, typename T>
struct has_template_argument<Container<T>> {
    static const bool value =
        !std::is_same<std::basic_string<T>, Container<T>>::value;
};

// Function to verify if the variable has a template argument
template <typename T>
bool has_template_arg(const T&) {
    return has_template_argument<T>::value;
}

// The test collection funtion.
template <typename T>
std::string test_container(const T& container) {
    std::stringstream listn;

    listn << "{";

    if (has_template_arg(container)) {
        auto it = container.begin();

        if (it != container.end()) {
            if (!has_template_arg(*it)) {
                listn << *it;

                std::cout << "\n";
            } else {
                // It's here that the problem starts.

                listn << test_container(*it);

                std::cout << "\nThis container is multi-dimentioned";
            }
            ++it;
        }

    } else {
        std::cout << "\nThis variable has no container to \"iterate\"";
    }

    listn << "}";

    return listn.str();
}

说明:

test_container
函数首先尝试查看传递的参数是否具有模板参数。

为此,它使用

has_template_arg
函数。
has_template_arg
对于所有具有 <> 的内容都返回 True,除了多个模板参数(目前)和字符串(预期,因为字符串不应该被视为容器)

函数中的

std::cout
仅用于证明该函数通常属于某种条件 (
if (!has_template_arg(*it))
);然而,当对当前函数进行递归调用时,它会忽略条件并错误地执行递归。

这证明问题显然不在

has_template_arg
检查器中:

std::map<int, int> mep = {{1, 2}, {4, 5}};
std::vector<std::vector<int>> vet = {{1, 2, 3}, {4, 5, 6}};
std::vector<std::string> strVet = {"hello", "world"};
std::string str = "Hi";
const char* chr = "Some char here";

// Works perfectly:
std::cout << "Is Vector container?: "      << has_template_arg(strVet) << std::endl;
std::cout << "Is Multivector container?: " << has_template_arg(vet)    << std::endl;

// This below CURRENTLY does not work: No problem for now, can wait.
std::cout << "Is Map container?: "         << has_template_arg(mep)    << std::endl;

// Also works:
std::cout << "Is Int container?: "        << has_template_arg(5)    << std::endl;
std::cout << "Is Char container?: "       << has_template_arg('o')  << std::endl;
std::cout << "Is Char[] container?: "     << has_template_arg("oi") << std::endl;
std::cout << "Is Const char container?: " << has_template_arg(chr)  << std::endl;
std::cout << "Is String container: "      << has_template_arg(str)  << std::endl;

std::cout    << "Vector Recursion: "       << test_container(strVet)  << std::endl;

// std::cout << "String Recursion: "       << test_container(str)     << std::endl;
// std::cout << "Multivector recursion: "  << foreach(vet)            << std::endl;
// std::cout << "Const char recursion: "   << test_container(chr)     << std::endl;

重要提示:如果注释掉代码将进入递归的行:

// listn << test_container(*it);

一切都会正常工作。

但是,它将无法根据需要检查传递的参数是否也应该“迭代”或应该发送到字符串流。

另一方面,如果不(评论),则会出现以下编译错误:

  1. 如果你只传递向量递归:
std::cout    << "Vector Recursion: "       << test_container(strVet)   << std::endl;

// std::cout << "String Recursion: "       << test_container(str)      << std::endl;

// std::cout << "Multivector recursion: "  << foreach(vet)             << std::endl;

// std::cout << "Const char recursion: "   << foreach(chr)             << std::endl;

这是结果:

Compiling single file...

--------

- Filename: C:\Users\user\Workstation\C\CPP-Projects\TesteCPP\Testes\ephemeral.cpp

- Compiler Name: TDM-GCC 4.9.2 64-bit Release



Processing C++ source file...

--------

- C++ Compiler: C:\Program Files (x86)\Dev-Cpp\MinGW64\bin\g++.exe

- Command: g++.exe [...]



C:\Users\user\Workstation\C\CPP-Projects\TesteCPP\Testes\ephemeral.cpp: In instantiation of 'std::string test_container(const T&) [with T = char; std::string = std::basic_string<char>]':

[...]\ephemeral.cpp:42:41:   recursively required from 'std::string test_container(const T&) [with T = std::basic_string<char>; std::string = std::basic_string<char>]'

[...]\ephemeral.cpp:42:41:   required from 'std::string test_container(const T&) [with T = std::vector<std::basic_string<char> >; std::string = std::basic_string<char>]'

[...]\ephemeral.cpp:100:60:   required from here

[...]\ephemeral.cpp:35:29: error: request for member 'begin' in 'container', which is of non-class type 'const char'

   auto it = container.begin();

                             ^



[...]\ephemeral.cpp:36:10: error: request for member 'end' in 'container', which is of non-class type 'const char'

   if (it != container.end()) {

          ^
  1. 如果您仅传递 '"String Recursion: " (...)':
// std::cout << "Vector Recursion: "       << test_container(strVet)    << std::endl;

std::cout    << "String Recursion: "       << test_container(str)       << std::endl;

// std::cout << "Multivector recursion: "  << foreach(vet)     << std::endl;

// std::cout << "Const char recursion: "   << test_container(chr)       << std::endl;

这就是你得到的:

Compiling single file...

--------

- Filename: C:\Users\user\Workstation\C\CPP-Projects\TesteCPP\Testes\ephemeral.cpp

- Compiler Name: TDM-GCC 4.9.2 64-bit Release



Processing C++ source file...

--------

[...]

[...]\ephemeral.cpp: In instantiation of 'std::string test_container(const T&) [with T = char; std::string = std::basic_string<char>]':

[...]\ephemeral.cpp:41:40:   required from 'std::string test_container(const T&) [with T = std::basic_string<char>; std::string = std::basic_string<char>]'

[...]\ephemeral.cpp:100:51:   required from here

[...]\ephemeral.cpp:35:29: error: request for member 'begin' in 'container', which is of non-class type 'const char'

   auto it = container.begin();

                             ^



C:\Users\user\Workstation\C\CPP-Projects\TesteCPP\Testes\ephemeral.cpp:36:10: error: request for member 'end' in 'container', which is of non-class type 'const char'

   if (it != container.end()) {

          ^

这是

foreach

// This is the "real" code I said (that also needs fix, although it works):

template <typename T>
std::string foreach(const T& collection) {
    std::stringstream listn;
    listn << "{";

    auto it = collection.begin();
    
    if (it != collection.end()) {
        if (has_template_arg(*it)) {
            // listn << foreach(*it);
        } else {
          listn << *it;
      }
        ++it;
    }

    while (it != collection.end()) {
        listn << "," << *it;
        ++it;
    }
    listn << "}";

    return listn.str();
}

我尝试过多个版本的 GNU C++ 编译器,它们都返回相同的错误消息,尽管存在一些差异。

这意味着,这是一个简单的语法错误 - 已知它从哪里开始,但不知道如何修复它。 预期: GCC g++ 编译代码,没有错误或警告。这意味着需要 test_container 函数知道在其中进行递归的时间。

由于您使用的是C++11,因此您无法解决未采取的分支无效的问题,

if constexpr
,否则这是我的建议。

c++ loops c++11 recursion gcc
1个回答
0
投票
foreach

函数并使用 SFINAE 确保选择正确的版本。

我会将您现在拥有的类型特征替换为检查提供的参数是否支持 

std::begin()

/

std::end()
的类型特征。我还会添加一种类型特征来检查提供的参数是否支持流式传输到

ostream

#include <type_traits>
#include <utility>

// void_t from c++20
// Early version to support old compilers that haven't implemented
// https://cplusplus.github.io/CWG/issues/1558.html
template<typename... Ts>
struct make_void { typedef void type; };
 
template<typename... Ts>
using void_t = typename make_void<Ts...>::type;
//----------------------------------------------------------------
// trait to check if T supports std::begin and std::end
template <class, class = void>
struct has_begin_and_end : std::false_type {};

template <class T>
struct has_begin_and_end<T, void_t<decltype(std::begin(std::declval<T&>()),
                                            std::end(std::declval<T&>()))>>
    : std::true_type {};

// trait to check if T supports streaming to an ostream
template <class, class = void>
struct can_ostream : std::false_type {};

template <class T>
struct can_ostream<
    T, void_t<decltype(std::declval<std::ostream&>() << std::declval<T>())>>
    : std::true_type {};
您的 
foreach
版本可能如下所示:

// char[N] version - trims off `\0` if there is one
template <std::size_t N>
std::string foreach (const char (&arr)[N]) {
    return {arr, arr + N - (arr[N - 1] == '\0')};
}

// std::string version, adds quotes around the string
std::string foreach (const std::string& str) {
    return '"' + str + '"';
}

// forward declaration of the std::pair version
template <class F, class S>
std::string foreach (const std::pair<F, S>& pair);

// generic other non-container versions that requires that T can be streamed
// and that T does _not_ support std::begin/std::end
template <class T>
typename std::enable_if<can_ostream<T>::value && !has_begin_and_end<T>::value,
                        std::string>::type
foreach (const T& var) {
    std::ostringstream listn;
    listn << var;
    return listn.str();
}

template <class T>  // container version:
typename std::enable_if<has_begin_and_end<T>::value,
                        std::string>::type
foreach (const T& container) {
    std::ostringstream listn;
    listn << '{';
    auto it = std::begin(container);
    auto end = std::end(container);
    if (it != end) {
        listn << foreach (*it);
        for (++it; it != end; ++it) {
            listn << ", " << foreach (*it);
        }
    }
    listn << '}';
    return listn.str();
}

template <class F, class S>  // pair version
std::string foreach (const std::pair<F, S>& pair) {
    return '{' + foreach (pair.first) + '=' + foreach (pair.second) + '}';
}

您可以在这个

演示
中看到它的工作原理。

© www.soinside.com 2019 - 2024. All rights reserved.