【问题标题】:C++ if (false) condition evaluated?C++ if (false) 条件评估?
【发布时间】:2019-08-02 21:17:38
【问题描述】:

我正在学习 C++ 中的模板,而我的 C++ 术语集有些有限,所以我无法在 Google 上搜索这个问题。

我正在尝试实现一个基于 dict 类型的自定义在std::unordered_map。我的目标是能够以如下方式实例化 dict 类:

dict<std::string, long> d; // OR
dict<std::string, std::set<std::string>> d; // OR
dict<std::string, std::map<char, float>> d; // OR
dict<std::string, std::vector<std::string>> d; // OR
dict<std::string, std::vector<double>> d;

这是我正在使用的代码:

utils.h

#include <fstream>
#include <unordered_map>
#include <set>
#include <vector>
#include <algorithm>
#include <type_traits>

// for bravity
using namespace std;

// to check for stl vector
// slightly modified version of: https://stackoverflow.com/a/31105859
namespace is_container {
    template <typename T> struct stl_vector : false_type{};
    template <typename T> struct stl_vector<std::vector<T>> : true_type{};
}

namespace StringOps {
    // generaic function to split based on many delimiters:
    // source: https://stackoverflow.com/a/9676623
    vector<string> split(const string& str, const string& delimiters = " ,") {
        vector<string> v;
        unsigned start = 0;
        auto pos = str.find_first_of(delimiters, start);
        while(pos != string::npos) {
            if(pos != start) // ignore empty tokens
                v.emplace_back(str, start, pos - start);
            start = pos + 1;
            pos = str.find_first_of(delimiters, start);
        }
        if(start < str.length()) // ignore trailing delimiter
            v.emplace_back(str, start, str.length() - start); // add what's left of the string
        return v;
    }
}

template<class Key, template <class...> class Value, typename T, class = void>
class dict {
    public:
        Value<T> t;
};


template<class Key, template <class...> class Value, typename T> // detect container types with ::iterator
class dict<Key, Value, T, void_t<typename Value<T>::iterator>> : true_type {
    private:
        unordered_map<Key, Value<T>> content;
        bool is_vector = false;
        string line;
        unordered_map<Key, Value<T>> load(ifstream& file) {
            while (getline(file, line)) {
                if (!line.empty()) {
                    // remove trailling \n if exists
                    if (line[line.length()-1] == '\n')
                        line.erase(line.length() - 1);

                    vector<string> tokens = StringOps::split(line);
                    Value<T> result;
(tokens[i]));

                    if (is_vector) {
                        for (unsigned i = 1; i < tokens.size(); i++) {
                            result.emplace_back(static_cast<T>(tokens[i]));
                        }
                    }
                    if(false) { // should never be looked into
                        auto it = result.cend();
                        for (unsigned i = 1; i < tokens.size(); i++) {
                            result.emplace_hint(it, static_cast<T>(tokens[i]));
                        }
                    }
                    content[static_cast<Key>(tokens[0])] = result;
                }
            }
            return content;
        }

    public:
        constexpr Value<T>& operator[](Key k) {
            return content[k];
        }

        dict(const string& path) {
            // detect vector type
            if(is_container::stl_vector<decay_t<Value<T>>>::value)
                is_vector = true;
            ifstream file(path);
            content = load(file);
        }

        constexpr unsigned size() {
            return content.size();
        }
};

template<class Key, template <class...T> class Value, typename T> // detect arithmatic types
class dict<Key, Value, T, typename enable_if<is_arithmetic<Value<T>>::value>::type> {
    public:
        dict() {
            // we'll come to you later..
        }
};

main.cpp

#include <iostream>
#include "utils.h"

int main() {
    dict<string, vector, string> d("/home/path/to/some/file");
    cout << d.size();
}

结果:

error: no member named 'emplace_hint' in 'std::vector<std::__cxx11::basic_string<char>, std::allocator<std::__cxx11::basic_string<char> > >'
                        result.emplace_hint(it, static_cast<T>(tokens[i]));

问题:
1 - 为什么首先达到if (false) 条件?
2 - 如何对其进行调整以实现所需的实例化风格?

【问题讨论】:

  • 条件未达到。事实上,没有什么是“达到”的;执行甚至没有开始,因为代码没有编译。
  • 程序必须格式正确才能编译 - 包括在运行时不会执行的部分。您不能编写 if (false) { random garbage } 并期望代码能够编译。实现(我认为)您想要实现的目标的一种方法是通过一个特征类,该类具有统一的接口和您想要支持的每个容器的专门化。
  • “无法访问”是运行时的事情。在代码可以运行之前,它必须被编译。为了被编译,它必须(除其他外)在语法上有效并通过类型检查。 if (false) { int x = "apples" * 1.5; } 是一个类型错误,所以它没有通过编译。 if (false) { fjakslfkdjfaklsdfjkalskdjf(); } 使用了一个未声明的标识符,所以它没有通过编译。
  • 我不明白那个评论。错误的代码不会因为你移动它而突然编译。
  • 不管你有没有提到,这就是if (false) {}的意思。意思是“程序运行时不要执行这个块”。这并不意味着“甚至不看这个块中的代码”。

标签: c++ c++11 dictionary conditional-statements


【解决方案1】:

if (false) 不代表代码没有被编译;它只是意味着里面的代码在运行时没有执行,但它仍然必须是有效的。

C++ 中有(至少)三种条件构造:

  1. 预处理器条件。如果条件不满足,这告诉预处理器不要将代码传递给编译器。因此,只要包含有效的预处理器标记,代码就可能完全是乱码。例如,以下是具有已定义行为的格式良好的 C++ 程序

    #include <iostream>
    
    int main()
    {
    #if 0
        YYMJBNvOldLdK8rC0PTXH8DHJ58FQpP0MisPZECDuYHDJ7xL9G
    #else
        std::cout << "Hello world!\n";
    #endif
    }
    
  2. 运行时选择语句。此类代码仍由编译器解析,并且无论编译器是否能够证明无法访问的代码都必须仍然是有效代码——如果不解析代码,编译器甚至无法找到终止的}。这部分是因为编译器无法在运行时评估任意表达式 - 如果您没有明确指定(参见下一个项目符号),则评估默认为运行时。因此,如果将#if 0#else#endif 替换为if (false) {} else {},上面的代码就会变得不正确。但是,无法访问的代码中的运行时错误(即未定义的行为)很好。因此,以下是具有已定义行为的格式良好的 C++ 程序:(某些编译器可能会生成警告,但这无关紧要)

    #include <iostream>
    #include <climits>
    
    int main()
    {
        if (false) {
            volatile int x = 42/0;
            x = *static_cast<int*>(nullptr);
            const int y = INT_MAX + 1;
        } else {
            std::cout << "Hello world!\n";
        }
    }
    
  3. (C++17 起)if constexpr。这个规则有点复杂。条件必须在编译时知道,false 分支被丢弃。丢弃的分支仍然需要有效,只是它没有被实例化。因此,如果您将if 更改为if constexpr,上述代码仍然是有效代码。以下也是具有已定义行为的格式良好的 C++ 程序:

    #include <iostream>
    #include <type_traits>
    
    template <typename T>
    void print(T x)
    {
        if constexpr (std::is_same_v<T, int>) {
            std::cout << static_cast<typename T::template stack<T>::overflow>(x);
        } else {
            std::cout << x;
        }
    }
    
    int main()
    {
        print("Hello world!\n");
    }
    

    typenametemplate 仍然是使代码语法有效所必需的,但不存在的类型 const char*::stack&lt;const char*&gt;::overflow 没有形成。


在您的情况下,您可以编写一个特征类来确定一个类型是否是类模板std::vector的特化:(这里我使用标准特征约定)

template <typename C>
struct is_std_vector :std::false_type {};
template <typename T, typename A>
struct is_std_vector<std::vector<T, A>> :std::true_type {};
template <typename C>
inline constexpr bool is_std_vector_v = is_std_vector<C>::value;

然后在if constexpr 中使用它来调度:(不要忘记将Container 替换为您正在检查的容器类型)

if constexpr (is_std_vector_v<Container>) {
    // do std::vector specific things
} else {
    // do other things
}

【讨论】:

  • @7kemZmani 在您的情况下,您似乎可以完全删除 if (is_vector) { /* ... */ } 部分。我错过了什么吗?
  • load 方法正在尝试使用文件中的数据填充容器,并尝试检测容器 is_vector 是否使用 emplace_back .. 和 emplace_hint 用于其他容器(我假设只有 stl 容器)
  • @7kemZmani 我已更新以包含 trait 类的示例。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多