1

In my project, there are some config files. I need to extract them either as integers or as strings. To do so, I write a function as below:

template<typename T>
void loadTxtConfig(const std::string& filename, std::set<T>& ids) {
    std::ifstream infile(filename);
    if (!infile) {
        return;
    }
    std::string line;
    if (std::is_integral<T>::value) {
        while (std::getline(infile, line)) {
            ids.emplace(static_cast<T>(std::stoll(line))); // noexcept
        }
    } else if (std::is_same<T, std::string>::value) {
        while (std::getline(infile, line)) {
            ids.emplace(line);
        }
    }
}

I try to cal it as below:

std::set<int> intCfg;
std::set<std::string> strCfg;
loadTxtConfig("./conf/int.txt", intCfg);
loadTxtConfig("./conf/str.txt", strCfg);

However, when I compile it, I always get an error about template:

/usr/include/c++/5/ext/new_allocator.h:120:4: error: cannot convert ‘std::__cxx11::basic_string<char>’ to ‘int’ in initialization
  { ::new((void *)__p) _Up(std::forward<_Args>(__args)...); }

What did I wrongly do?

Yves
  • 11,597
  • 17
  • 83
  • 180
  • 1
    When instantiating a function, every line must be valid (possible to compile), even if it will never be reached. Your code doesn't meet that assumption. You can use `if constexpr` like in the answer or (pre C++17) you will need to overload your function and provide different versions for different types. – Yksisarvinen Aug 12 '21 at 09:15
  • when you need the function for only 2 types (or a limited set of types in general) then overloads are simpler than templates – 463035818_is_not_an_ai Aug 12 '21 at 09:19

3 Answers3

4

The problem is, both the statement-true and statement-false of if need to be valid at compile-time, even one of them won't be evaluated at run-time. When T is int ids.emplace(line); causes the error.

You could use Constexpr If (since C++17).

If the value is true, then statement-false is discarded (if present), otherwise, statement-true is discarded.

if constexpr (std::is_integral<T>::value) {
    while (std::getline(infile, line)) {
        ids.emplace(static_cast<T>(std::stoll(line))); // noexcept
    }
} else if constexpr (std::is_same<T, std::string>::value) {
    while (std::getline(infile, line)) {
        ids.emplace(line);
    }
}

Before C++17 you can apply overloading. E.g.

template<typename T>
void loadTxtConfig(const std::string& filename, std::set<T>& ids) {
    std::ifstream infile(filename);
    if (!infile) {
        return;
    }
    std::string line;
    while (std::getline(infile, line)) {
        ids.emplace(line);
    }
}

void loadTxtConfig(const std::string& filename, std::set<int>& ids) {
    std::ifstream infile(filename);
    if (!infile) {
        return;
    }
    std::string line;
    while (std::getline(infile, line)) {
        ids.emplace(static_cast<T>(std::stoll(line))); // noexcept
    }
}
songyuanyao
  • 169,198
  • 16
  • 310
  • 405
  • https://stackoverflow.com/questions/68750800/typeid-vs-stdis-same-vs-if-constexpr. So if I have to work with C++11, I have to use `typeid` in this case, right? – Yves Aug 12 '21 at 09:15
  • @Yves no, you don't have to. The pre C++17 way is to specialize the template – 463035818_is_not_an_ai Aug 12 '21 at 09:18
  • @Yves `typeid` works at run-time too. I think you need perform specialization or overloading on `loadTxtConfig`. – songyuanyao Aug 12 '21 at 09:18
  • @463035818_is_not_a_number Alright, looks like I have no choice. – Yves Aug 12 '21 at 09:19
  • 1
    You might create sub-function to avoid to duplicate common code though for pre-c++17. a `T convert_to(tag, const std::string&)` seems enough. – Jarod42 Aug 12 '21 at 09:24
4

In C++11 I suggest that you break out the parameter specific part (the conversion) and use SFINAE (substitution failure is not an error) to make use of the correct one:

#include <type_traits>

template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
T convert(const std::string& line) {
    return static_cast<T>(std::stoll(line)); // noexcept
}

template<typename T, typename std::enable_if<std::is_same<T, std::string>::value, int>::type = 0>
const T& convert(const std::string& line) {
    return line;
}

template<typename T>
void loadTxtConfig(const std::string& filename, std::set<T>& ids) {
    std::ifstream infile(filename);
    if (!infile) return;
    std::string line;
    while (std::getline(infile, line)) {
        ids.emplace(convert<T>(line));
    }
}
Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
2

In C++11 you must provide different versions of the function for different types.

void loadTxtConfig(const std::string& filename, std::set<std::string>& ids) {
    std::ifstream infile(filename);
    if (!infile) {
        return;
    }
    std::string line;
    while (std::getline(infile, line)) {
        ids.emplace(line);
    }
}

//you could add std::enable_if to only limit the function to integral types, 
//but it will only compile if `static_cast<T>(long long)` is valid
template<typename T>
void loadTxtConfig(const std::string& filename, std::set<T>& ids) {
    std::ifstream infile(filename);
    if (!infile) {
        return;
    }
    std::string line;
    while (std::getline(infile, line)) {
        ids.emplace(static_cast<T>(std::stoll(line))); // noexcept
    }
}
Yksisarvinen
  • 18,008
  • 2
  • 24
  • 52