user6256186 user6256186 - 4 years ago 59
C++ Question

Template functions strange linker error

Consider this simple join function implementation.

#include <iostream>
#include <string>

namespace detail {
template<typename... Args>
size_t calcSize(const Args&... args);

inline size_t calcSize(const std::string& head) {
return head.size();
}

template<size_t N>
size_t calcSize(char const(&) [N]) {
return N - 1;
}

template<typename... Tail>
size_t calcSize(const std::string& head, const Tail&... tail) {
return head.size() + calcSize(tail...);
}

template<size_t N, typename... Tail>
size_t calcSize(char const(&) [N], const Tail&... tail) {
return N - 1 + calcSize(tail...);
}

template<typename... Args>
void fillResult(std::string& result,
size_t startIndex,
const Args&... args);

inline void fillResult(std::string& result,
size_t startIndex,
const std::string& head) {
for (size_t i = 0; i < head.size(); ++i) {
if (head[i] == '\0') {
break;
}
result[startIndex++] = head[i];
}
}

template<size_t N>
void fillResult(std::string& result,
size_t startIndex,
char const(&head) [N]) {
for (size_t i = 0; i < N; ++i) {
if (head[i] == '\0') {
break;
}
result[startIndex++] = head[i];
}
}

template<typename... Tail>
void fillResult(std::string& result,
size_t startIndex,
const std::string& head,
const Tail&... tail) {
for (size_t i = 0; i < head.size(); ++i) {
if (head[i] == '\0') {
break;
}
result[startIndex++] = head[i];
}
fillResult(result, startIndex, tail...);
}

template<size_t N, typename... Tail>
void fillResult(std::string& result,
size_t startIndex,
char const(&head) [N],
const Tail&... tail) {
for (size_t i = 0; i < N; ++i) {
if (head[i] == '\0') {
break;
}
result[startIndex++] = head[i];
}
fillResult(result, startIndex, tail...);
}
}

template<typename... Args>
std::string join(const Args&... args) {
std::string result;
result.resize(detail::calcSize(args...));
detail::fillResult(result, 0, args...);
return result;
}


int main() {
std::cout << join("ab", "cd", "ef", "gh") << std::endl;
std::cout << join(std::string("ab"), std::string("cd"), std::string("ef")) << std::endl;
std::cout << join(std::string("ab"), "cd") << std::endl;
//std::cout << join(std::string("ab"), "cd", "ef") << std::endl;
//std::cout << join(std::string("ab"), "cd", std::string("ef")) << std::endl;
return 0;
}


It works fine for the three first lines in main and fails with linker error if you uncomment any of two last invocations. Tried with gcc 4.9 and clang with the same result. Can anyone point out what's wrong?
Here is a link to coliru http://coliru.stacked-crooked.com/a/f55aa64fb4861e43

Answer Source

I think this is basically an ill-formed program, because you end up using an overload that wouldn't be used if all overloads had been visible at the time of use. This is because the overload you want isn't actually declared yet at the point where it is needed. To fix this, just add all the declarations up front:

namespace detail {

template<typename... Args>
size_t calcSize(const Args&... args);

template<size_t N, typename... Tail>
size_t calcSize(char const(&) [N], const Tail&... tail);

template<typename... Tail>
size_t calcSize(const std::string& head, const Tail&... tail);

template<typename... Tail>
void fillResult(std::string& result, 
                size_t startIndex,
                const std::string& head,
                const Tail&... tail);

template<size_t N, typename... Tail>
void fillResult(std::string& result,
                size_t startIndex,
                char const(&head) [N],
                const Tail&... tail);
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download