2

I'm learning about template in C++, and my set of C++ terminology is somewhat limited, so I couldn't google this problem.

I'm trying to implement a custom dict type based on std::unordered_map. My goal is to be able to instantiate the class dict in ways like the following:

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;

So here's the code, I'm using:

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();
}

results:

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]));

questions:
1 - why on earth if (false) condition is reached in the first place?
2 - how could this be tweaked to achieve the desired instantiation style?

7kemZmani
  • 658
  • 1
  • 8
  • 21
  • 2
    The condition is not reached. In fact, nothing is "reached"; execution doesn't even start because the code doesn't compile. – melpomene Aug 02 '19 at 21:20
  • 1
    The program must be well-formed in order to compile - including parts of it that will not be executed at run time. You cannot write `if (false) { random garbage }` and expect the code to compile. One way to achieve what (I think) you are trying to achieve is via a traits class, with a uniform interface and a specialization for every container you want to support. – Igor Tandetnik Aug 02 '19 at 21:30
  • @IgorTandetnik you can swap `if (false)` for `else` ... and still get the same error; when you clearly pass `vector` – 7kemZmani Aug 02 '19 at 21:35
  • @melpomene why then g++ is complaining about `.emplace_hint` when it's inside an "unreachable" condition?? I'd appreciate any insight. – 7kemZmani Aug 02 '19 at 21:36
  • also,from your comments, I may figure out a better title, but feel free to suggest a better more informative title or description of the problem. – 7kemZmani Aug 02 '19 at 21:42
  • 1
    "Unreachable" is a runtime thing. Before code can run, it has to be compiled. In order to be compiled, it must (among other things) be syntactically valid and pass type checking. `if (false) { int x = "apples" * 1.5; }` is a type error, so it doesn't pass compilation. `if (false) { fjakslfkdjfaklsdfjkalskdjf(); }` uses an undeclared identifier, so it doesn't pass compilation. – melpomene Aug 02 '19 at 21:44
  • @melpomene okay. But even if `if (flase)` is changed to `else` so that it compiles (I think) the same error message will appear in both g++ and clang++ – 7kemZmani Aug 02 '19 at 21:47
  • 1
    I don't understand that comment. Wrong code doesn't suddenly compile just because you move it around. – melpomene Aug 02 '19 at 21:49
  • 1
    You can't write `if (true){} else { random garbage }` either. Basically, you can't write `random garbage`, period. Whether that spot is reachable at run time or not is irrelevant. In fact, it's meaningless to even talk about "reachable at runtime" - there won't be any runtime when the code fails to compile in the first place. If you want to hide a piece of code from compilation, turn it into a comment, or surround with `#if 0' / `#endif` – Igor Tandetnik Aug 02 '19 at 21:49
  • @IgorTandetnik easy bro. I didn't mention the word runtime at all. The word appeared on CLion, and it stuck in my head. – 7kemZmani Aug 02 '19 at 21:52
  • 5
    Whether you mention it or not, that's what `if (false) {}` means. It means "don't execute this block when the program runs". It doesn't mean "don't even look at the code within this block". – Igor Tandetnik Aug 02 '19 at 21:54
  • @melpomene I get your point .. the code doesn't compile. I hope you both can see why I'm asking, the error message is not informative at all (at least to me) and a proper way to achieve my intended behaviour is greatly appreciated. – 7kemZmani Aug 02 '19 at 21:55
  • I don't understand the question. You never mentioned any intended behavior. Why can't you just use a `std::map`? – melpomene Aug 02 '19 at 21:59
  • As I mentioned earlier, you probably want a traits class. See e.g. [here](https://erdani.com/publications/traits.html) and [here](http://www.gotw.ca/publications/mxc++-item-4.htm) – Igor Tandetnik Aug 02 '19 at 22:00
  • Why do you consider the error message as not helpful? It tells you that a certain class does not have a certain member which you are trying to access. Which is exactly the problem. – Aziuth Aug 03 '19 at 23:05

1 Answers1

3

if (false) doesn't mean the code isn't compiled; it simply means the code inside is not executed at runtime, but it still has to be valid.

There are (at least) three kinds of conditional constructs in C++:

  1. Preprocessor conditions. This tells the preprocessor not to pass the code to the compiler if if the condition is not met. Therefore, the code can be completely gibberish as long as it consists of valid preprocessor tokens. For example, the following is a well-formed C++ program with defined behavior:

    #include <iostream>
    
    int main()
    {
    #if 0
        YYMJBNvOldLdK8rC0PTXH8DHJ58FQpP0MisPZECDuYHDJ7xL9G
    #else
        std::cout << "Hello world!\n";
    #endif
    }
    
  2. Runtime selection statements. Such code is still parsed by the compiler and must still be valid code regardless of whether the compiler is capable of proving unreachable code — the compiler cannot even find the terminating } if it doesn't parse the code. This is partly because compilers cannot evaluate arbitrary expressions at runtime — if you don't specify explicitly (see next bullet), then evaluation defaults to be runtime. Therefore, the code above becomes ill-formed if you replace #if 0#else#endif with if (false) {} else {}. However, runtime errors (i.e., undefined behavior) within unreachable code are fine. Therefore, the following is a well-formed C++ program with defined behavior: (some compilers may generate warnings, but that's irrelevant)

    #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. (Since C++17) if constexpr. The rule for this one is a bit complex. The condition has to be known at compile time, and the false branch is discarded. The discarded branch is still required to be valid, except that it is not instantiated. Therefore, the code above is still valid code if you change if to if constexpr. The following is also a well-formed C++ program with defined behavior:

    #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");
    }
    

    The typename and template are still necessary to make the code syntactically valid, but the nonexistent type const char*::stack<const char*>::overflow is not formed.


In your case, you can write a trait class to determine whether a type is a specialization of the class template std::vector: (here I use the standard traits convention)

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;

Then use it in if constexpr to dispatch: (don't forget to replace Container with the container type you are examining)

if constexpr (is_std_vector_v<Container>) {
    // do std::vector specific things
} else {
    // do other things
}
L. F.
  • 19,445
  • 8
  • 48
  • 82
  • @7kemZmani In your case, it seems that you can remove the `if (is_vector) { /* ... */ }` part altogether. Am I missing something? – L. F. Aug 04 '19 at 01:05
  • the `load` method is trying to populate the container with data from the file, and tries to detect if the container `is_vector` for which it uses `emplace_back` .. and `emplace_hint` for others (I'm assuming stl containers only) – 7kemZmani Aug 04 '19 at 01:08
  • 1
    @7kemZmani I have updated to include an example of the trait class. – L. F. Aug 04 '19 at 01:20