Reputation: 3346
I want to write a function for any number of parameters,
inline void show_log(const char* s)
{
std::cout << s << std::endl;
}
And I write it as https://en.cppreference.com/w/cpp/language/parameter_pack instructs.
inline void show_log(const char* s)
{
std::cout << s << std::endl;
}
template<typename T, typename... Targs>
inline void show_log(T s, Targs ... args)
{
show_log(s);
show_log(args...);
}
inline void test()
{
show_log("abc", "ttt", "ccc");
}
It works fine. But I want more strict – make it only accepts const char*
parameters. I tried this:
Declare a general template function (but not implement it.)
template<typename T, typename... Targs>
inline void show_log(T s, Targs ... args);
Then implement a specilization only for const char*
template<const char*, typename... Targs>
inline void show_log(const char* s, Targs ... args)
{
std::cout<<"show_log spelicalized for const char*\n"
show_log(s);
show_log(args...);
}
Call the function,
inline void test()
{
show_log("abc", "ttt", "ccc");
}
It didn't compile with LNK2019 `show_log(const char*, const char*, const char*)' didn't implement error.
Upvotes: 0
Views: 140
Reputation: 365
The linker error that you're seeing is caused by the fact that you're creating an overload, not a specialization. Functions must be fully specialized since they cannot be overloaded. Classes are allowed to be partially specialized since they cannot be overloaded.
That said, you need to impose a constraint to your template arguments to achieve this behavior.
First, you need to define a meta-function:
// Returns true if all types are the same
// Returns false if any types are different
// Fails to compile if 0 or 1 arguments are passed in
template<typename first_t, typename ... rest_t>
struct is_all_same : std::conjunction<std::is_same<first_t, rest_t>...> {};
template<typename ... types_t>
constexpr auto is_all_same_v = is_all_same<types_t...>::value;
And then you can use the meta function in a number of ways to restrict your function usage.
// Using a static assert
template<typename T, typename... Targs>
inline void show_log(T s, Targs ... args)
{
static_assert(is_all_same_v<const char*, T, Targs...>, "Arguments must be of type: const char*");
show_log(s);
show_log(args...);
}
// requires clause is only available in c++20
template<typename T, typename... Targs> requires is_all_same_v<const char*, T, Targs...>
inline void show_log(T s, Targs ... args)
{
show_log(s);
show_log(args...);
}
// Use SFINAE (do yourself a favor and use C++ requires instead of this if it's available to you)
template<typename T, typename... Targs>
inline std::enable_if_t<is_all_same_v<const char*, T, Targs...>> show_log(T s, Targs ... args)
{
show_log(s);
show_log(args...);
}
One thing to note, is that you appear to be using recursion to accomplish this. However, you do not need to use recursion here if you have C++17 available (which I assume you do, given the tag). I would recommend doing something like this for c++17:
inline void show_log(const char* s)
{
std::cout << s << std::endl;
}
// No recursion. Be nice to your compiler when you can
template<typename ... args_t>
inline auto show_log(args_t ... args)
{
static_assert(is_all_same_v<const char*, args_t...>, "Arguments to show_log must have the type: const char*");
(show_log(args), ...);
}
If you are going to be using variadics (and templates in general), I would highly recommend keeping the above meta function handy. I use it everywhere in my template code. Understand it and digest it, so you can add more meta functions like it to your tool belt.
Edit: As Keijo excellently points out below, you may actually be interested in the single-function implementation of your pattern. Combining all of the recommendations on this thread yields you with something like this:
// Returns true if all types are the same
// Returns false if any types are different
// Fails to compile if 0 or 1 arguments are passed in
template<typename first_t, typename ... rest_t>
struct is_all_same : std::conjunction<std::is_same<first_t, rest_t>...> {};
template<typename ... types_t>
constexpr auto is_all_same_v = is_all_same<types_t...>::value;
template<typename ... args_t>
auto show_log(args_t ... args) noexcept -> void
{
using wanted_type = const char*;
static_assert(is_all_same_v<wanted_type, args_t...>, "Arguments must be of type: const char*");
((std::cout << args << '\n'), ...) << std::flush;
}
This solution:
Upvotes: 3
Reputation: 174
If you don't need endl between the arguments, you can do without specialization.
using WantedType = const char *;
template <class... Args>
std::enable_if_t<std::conjunction_v<std::is_same<WantedType, Args>...>>
show_log(Args... args)
{
(std::cout << ... << args) << std::endl;
}
Can anyone think of how to add linefeeds into this solution without specialization? Maybe it is not possible?
--- EDIT ---
As Christopher points out in comments, there is a neat single function solution:
using WantedType = const char *;
template <class... Args>
std::enable_if_t<std::conjunction_v<std::is_same<WantedType, Args>...>>
show_log(Args... args)
{
((std::cout << args << '\n'), ...) << std::flush;
}
Upvotes: 2