1

For the first quesion:

I want to write a function to concatenation the strings, and it can receive multiple strings;

#include <string>
#include <vector>
#include <type_traits>

template <class... Args, typename std::enable_if<std::is_same<typename std::decay<Args...>::type, std::string>::type>::type>
std::string foo(const std::string &first, const Args &... senconds) {
    std::string delimiter = "$$";
    std::string ret = first;
    std::vector<std::string> vec{senconds...};
    for (auto second = vec.rbegin(); second != vec.rend(); second++) {
        ret = delimiter + *second + delimiter + ret;
    }
    return ret;
}

but when I invoke it like:

std::string name = "x";
name = foo(name, "xxx");

the compiler will throw an error:

error: no matching function for call to ‘foo(std::__cxx11::string&, const char [4])’

and there will be some note:

note: couldn't deduce template parameter ‘<anonymous>’

I think I should modify the constraint in the template, and I've tried all the related methods in the type_traits, but none of them works.

For the second question:

I want to hide the implementation of some function, but for the template function, it's unable to put the definition in the .hpp, and put the implementation in the .cpp, the compiler will throw a undefined reference error. Is there any elegant way to solve this?

Thanks.

Patrick
  • 181
  • 1
  • 1
  • 11
  • For the second question, please see https://stackoverflow.com/questions/495021/why-can-templates-only-be-implemented-in-the-header-file (and don't get discouraged by the question title). But please also refrain from asking two separate questions at once. – Max Langhof Nov 28 '19 at 09:57
  • Aside: you don't need every element of `Args...` to be `std::string`, only something that you can instantiate `std::string` with – Caleth Nov 28 '19 at 10:20
  • @MaxLanghof, I'll split the two separate questions next time. – Patrick Nov 29 '19 at 02:19

2 Answers2

1

There's a bit to unwrap here.

  • std::decay<Args...>::type can't work. std::decay takes only a single template argument, but you attempt to expand the pack here. The expansion needs to happen on the is_same.

  • You are also missing a way to aggregate all the is_same predicates. Do you want to and them all or or them all? Presumably and. In C++17 that's easily done with a fold expression, but for C++11 we have to work a bit.

  • Finally the thing the compiler complains about: std::enable_if<bla>::type evaluates to void if bla is true. That means you're formally expecting a non-type template argument, and the compiler complains because it can't deduce which value of type void it should deduce. This is normally alleviated by forming a pointer to it instead and defaulting it to nullptr: std::enable_if<bla>::type* = nullptr.

  • It appears (?) that you expect foo(someString, "stringLiteral"); to work. It won't, because a string literal is not a std::string. Maybe you wanted a different predicate, but for this answer I'll stick with the original condition.


Putting all that together:

  • In C++17, you would write

    template <class... Args,
        std::enable_if_t<
                (std::is_same_v<std::decay_t<Args>, std::string> && ...)
            >* = nullptr
        >
    

    https://godbolt.org/z/84Dcmt

  • In C++11, we use this helper and add back the typename and ::type verbosity:

    template <class... Args,
        typename std::enable_if<
            var_and<
                std::is_same<typename std::decay<Args>::type, std::string>::value...
                >::value
            >::type* = nullptr
        >
    

    https://godbolt.org/z/2eFyX7

Max Langhof
  • 23,383
  • 5
  • 39
  • 72
  • I've tried the c++11 demo, but if the foo(..) is invoked like foo(name, "xxx"), it still can't find the correct template implementation. – Patrick Nov 29 '19 at 02:21
  • `:36:12: error: no matching function for call to 'foo' name = foo(name, stringRef, name, stringRef, "xxx");` I've modified the code like that. – Patrick Nov 29 '19 at 02:22
  • And for when I applied this code to my project, there's an error: `error: no type named ‘type’ in ‘struct std::enable_if’` – Patrick Nov 29 '19 at 02:28
  • @Patrick As I said, with `is_same` you won't be able to pass string literals (see last bullet point). And the error is presumably because you're trying to use it improperly, but I can't diagnose it without seeing what you did. But I see that you figured out something that works for you so, that's good. – Max Langhof Nov 29 '19 at 08:59
0

Base on MaxLanghof's answer, I changed the template to:

template <class... Args,
    typename std::enable_if<var_and<std::is_constructible<
        std::string, Args>::value...>::value>::type * = nullptr>

In this form, the function foo can be invoked like the name = foo(name, stringRed, "xxx").

Thanks @MaxLanghof again.

Patrick
  • 181
  • 1
  • 1
  • 11