How to avoid decay with template parameter deduction - c++

Simplified:
// CHAR_TYPE == char, wchar_t, ...
template <typename CHAR_TYPE, unsigned CHAR_COUNT>
void Foo(CHAR_TYPE const (&value)[CHAR_COUNT]) noexcept
{
TRACE("const ref array");
// perform a bit of logic and forward...
}
template <typename CHAR_TYPE>
void Foo(CHAR_TYPE const* value) noexcept
{
TRACE("const ptr");
// perform a bit of logic and forward...
}
// [ several other overloads ]
Callsite:
char const* ptr = ...
wchar_t const* wptr = ...
Foo(ptr); // <-- good: "const ptr"
Foo(wptr); // <-- good: "const ptr"
constexpr char const buffer[] { "blah blah blah" };
constexpr wchar_t const wbuffer[] { L"blah blah blah" };
Foo(buffer); // <-- ambiguous
Foo(wbuffer); // <-- ambiguous
Of course, I could remove the const ref array overload. However I would like to handle these types differently. I have tried to conditionally enable the correct overload, but I have not been able to determine the necessary condition.
template <typename CHAR_TYPE, unsigned COUNT>
typename std::enable_if</* std::is_?? */, void>::type
Foo(CHAR_TYPE const (&value)[COUNT]) noexcept
{
TRACE("by ref array");
// perform a bit of logic and forward...
}
template <typename CHAR_TYPE>
typename std::enable_if</* std::is_?? */, void>::type
Foo(CHAR_TYPE const* value) noexcept
{
TRACE("ptr");
// perform a bit of logic and forward...
}
What is the best way to disambiguate these overloads?
(I would prefer not to use an array wrapper)

One idea that works is to remove the pointer and simply have T instead, with a std::enable_if_t<std::is_pointer<T>::value> guard. Simplified example below:
#include <iostream>
#include <type_traits>
template<class T, size_t N>
void f(T const (&) [N])
{
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
template<class T, std::enable_if_t<std::is_pointer<T>::value>* = nullptr >
void f(T)
{
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
int main()
{
const char* str = "test";
char str2[]{"test2"};
f(str);
f(str2);
}
Live on Coliru

Taking the argument by (const) reference blocks array-to-pointer decay during template argument deduction. See [temp.deduct.call]/2. So:
template <typename CHAR_TYPE>
void Foo(CHAR_TYPE const* const & value) noexcept
{
TRACE("const ptr");
// perform a bit of logic and forward...
}

Related

Same template specialization for const & non const type

I have a following code:
#include <iostream>
class A
{};
class B
{};
template<typename T>
void Do(T data)
{
std::cout << "Do() default\n";
}
template<>
void Do(A* data)
{
std::cout << "Do(A*)\n";
}
template<>
void Do(B* data)
{
std::cout << "Do(B*)\n";
}
int main(int argc, char* argv[])
{
A* a = nullptr;
B* b = nullptr;
const A* aConst = nullptr;
const B* bConst = nullptr;
Do(a);
Do(aConst);
Do(b);
Do(bConst);
return 0;
}
which outputs:
Do(A*)
Do() default
Do(B*)
Do() default
How should I rewrite the code to share template specialization for const & non-const type without copy pasting the specialization with const keyword specifier so it produces output:
Do(A*)
Do(A*)
Do(B*)
Do(B*)
Instead of specializing, you can overload. Using
template<typename T, std::enable_if_t<std::is_same_v<std::decay_t<T>, A>, bool> = true>
void Do(T* data)
{
std::cout << "Do(A*)\n";
}
template<typename T, std::enable_if_t<std::is_same_v<std::decay_t<T>, B>, bool> = true>
void Do(T* data)
{
std::cout << "Do(B*)\n";
}
These functions will be called when you pass const A*/A*/const B*/B* since they are a better match then the generic template. The reason they are a better match is because T is more constrained. It is considered more specialized and so it will win in a tie breaker with the generic template in overload resolution.
C++14
I upvoted this answer which is a good showcase for SFINAE. To extend on it, you can simplify the SFINAE expression with std::is_convertible. This is more analogous how overload resolution would work (adding const qualifier).
template<typename T, std::enable_if_t<std::is_convertible_v<T*, const A*>, int> = 0>
void Do(T* data)
{
std::cout << "Do(A*)\n";
}
template<typename T, std::enable_if_t<std::is_convertible_v<T*, const B*>, int> = 0>
void Do(T* data)
{
std::cout << "Do(B*)\n";
}
C++17
As a bonus, in C++17 with constexpr if, you can use a single function for all cases:
template<typename T>
void Do(T data)
{
if constexpr (std::is_convertible_v<T, const A*>)
std::cout << "Do(A*)\n";
else if constexpr (std::is_convertible_v<T, const B*>)
std::cout << "Do(B*)\n";
else
std::cout << "Do() default\n";
}
You could use this pattern, if you are willing to write a small stub:
template<>
void Do(const A* data)
{
std::cout << "Do(A*)\n";
}
template<>
void Do(A* data)
{
Do((const A*)data);
}
The main code you don't want to duplicate uses const A*, because, well, you want it to work on const data too. The non-const one simply forward to it.

Function template argument deduction with user-defined conversion operator

Let's say I have a class that wraps a string literal:
template <size_t N>
class literal {
public:
constexpr literal(const char(&s)[N+1]) : wrapped_(s) {}
constexpr const char * c_str() const { return wrapped_; }
constexpr size_t size() const { return N; }
private:
const char (&wrapped_)[N+1];
};
template <size_t N>
literal<N-1> make_literal(const char (&s)[N]) { return literal<N-1>(s); }
Now, I'd like for instances of this wrapped string type to be convertible back to a const char[N] implicitly, in a way I can still access its size. I'd like to be able to do something like:
template <size_t N>
void foo(const char(&s)[N]) {
std::cout << N << ": " << s << std::endl;
}
int main() {
constexpr auto s = make_literal("testing");
foo(s);
}
My goal is to have one function defined for foo() that can accept actual string literals as well as wrapped literals. I've tried adding a user-defined conversion operator to the class definition:
using arr_t = char[N+1];
constexpr operator const arr_t&() const { return wrapped_; }
But this gives me the following with clang:
candidate template ignored: could not match 'const char [N]' against 'const literal<7>'
If I change the call to foo() to the following, it works:
foo((const char(&)[8])s);
...which means that the conversion operator works, but not in the context of template argument deduction. Is there any way I can make this work without defining foo() specifically to take a wrapped literal?
The problem you are having is templates never do conversions of the parameters. Since you give it a const literal<7>, that is all it has to work with.
The easy fix for this is to add an overload and then do the cast in the overload to call your string literal version. That should look like
template <size_t N>
void foo(const literal<N> &lit) {
foo(static_cast<typename literal<N>::arr_t&>(lit)); // explicitly cast to the array type alias
}
which gives you a full example of
template <size_t N>
class literal {
public:
constexpr literal(const char(&s)[N+1]) : wrapped_(s) {}
constexpr const char * c_str() const { return wrapped_; }
constexpr size_t size() const { return N; }
using arr_t = const char[N+1]; // <- Add const here since literals are const char[N]
constexpr operator const arr_t&() const { return wrapped_; }
private:
const char (&wrapped_)[N+1];
};
template <size_t N>
constexpr literal<N-1> make_literal(const char (&s)[N]) { return literal<N-1>(s); }
template <size_t N>
void foo(const char(&s)[N]) {
std::cout << N << ": " << s << std::endl;
}
template <size_t N>
void foo(const literal<N> &lit) {
foo(static_cast<typename literal<N>::arr_t&>(lit)); // explicitly cast to the array type alias
}
int main() {
constexpr auto s = make_literal("testing");
foo(s);
}
Yes, you are adding an overload but all of the important code does not have to be duplicated.
If you can use C++17 and you don't mind a little indirection you could do all of this with one function using a std::string_view and providing literal with a operator std::string_view. That would look like
template <size_t N>
class literal {
public:
constexpr literal(const char(&s)[N+1]) : wrapped_(s) {}
constexpr const char * c_str() const { return wrapped_; }
constexpr size_t size() const { return N; }
using arr_t = const char[N+1];
constexpr operator std::string_view() const { return wrapped_; }
private:
const char (&wrapped_)[N+1];
};
template <size_t N>
constexpr literal<N-1> make_literal(const char (&s)[N]) { return literal<N-1>(s); }
void foo(std::string_view s) {
std::cout << s.size() << ": " << s << std::endl;
}
int main() {
constexpr auto s = make_literal("testing");
foo(s);
foo("testing");
}

Typesafe variadic function

I want to write a function that accepts a variable number of string literals. If I was writing in C, I would have to write something like:
void foo(const char *first, ...);
and then the call would look like:
foo( "hello", "world", (const char*)NULL );
It feels like it ought to be possible to do better in C++. The best I have come up with is:
template <typename... Args>
void foo(const char* first, Args... args) {
foo(first);
foo(args);
}
void foo(const char* first) { /* Do actual work */ }
Called as:
foo("hello", "world");
But I fear that the recursive nature, and the fact that we don't do any type checking until we get to a single argument, is going to make errors confusing if somebody calls foo("bad", "argument", "next", 42). What I want to write, is something like:
void foo(const char* args...) {
for (const char* arg : args) {
// Real work
}
}
Any suggestions?
Edit: There is also the option of void fn(std::initializer_list<const char *> args), but that makes the call be foo({"hello", "world"}); which I want to avoid.
I think you probably want something like this:
template<class... Args,
std::enable_if_t<(std::is_same_v<const char*, Args> && ...), int> = 0>
void foo(Args... args ){
for (const char* arg : {args...}) {
std::cout << arg << "\n";
}
}
int main() {
foo("hello", "world");
}
Note: it is not possible to match just string literals. The closest you can come is to match a const char array.
To do the type checking, use a function template which takes const char arrays.
To loop over them with range-based for, we need to convert it to an initializer_list<const char*>. We can do so directly with braces in the range-based for statement, because arrays will decay to pointers.
Here is what the function template looks like (note: this works on zero or more string literals. If you want one or more, change the function signature to take at least one parameter.):
template<size_t N>
using cstring_literal_type = const char (&)[N];
template<size_t... Ns>
void foo(cstring_literal_type<Ns>... args)
{
for (const char* arg : {args...})
{
// Real work
}
}
While all other answers solve the problem, you could also do the following:
namespace detail
{
void foo(std::initializer_list<const char*> strings);
}
template<typename... Types>
void foo(const Types... strings)
{
detail::foo({strings...});
}
This approach seems (at least to me) to be more readable than using SFINAE and works with C++11. Moreover, it allows you to move implementation of foo to a cpp file, which might be useful too.
Edit: at least with GCC 8.1, my approach seems to produce better error message when called with non const char* arguments:
foo("a", "b", 42, "c");
This implementation compiles with:
test.cpp: In instantiation of ‘void foo_1(const ArgTypes ...) [with ArgTypes = {const char*, int, const char*, const char*}]’:
test.cpp:17:29: required from here
test.cpp:12:16: error: invalid conversion from ‘int’ to ‘const char*’ [-fpermissive]
detail::foo({strings...});
~~~~~~~~~~~^~~~~~~~~~~~~~
While SFINAE-based (liliscent's implementation) produces:
test2.cpp: In function ‘int main()’:
test2.cpp:14:29: error: no matching function for call to ‘foo(const char [6], const char [6], int)’
foo("hello", "world", 42);
^
test2.cpp:7:6: note: candidate: ‘template<class ... Args, typename std::enable_if<(is_same_v<const char*, Args> && ...), int>::type <anonymous> > void foo(Args ...)’
void foo(Args... args ){
^~~
test2.cpp:7:6: note: template argument deduction/substitution failed:
test2.cpp:6:73: error: no type named ‘type’ in ‘struct std::enable_if<false, int>’
std::enable_if_t<(std::is_same_v<const char*, Args> && ...), int> = 0>
+1 for the C++17 liliscent's solution.
For a C++11 solution, a possible way is create a type traits to make an "and" of multiple values (something similar to std::conjunction that, unfortunately, is available only starting from C++17... when you can use folding and you don't need std::conjunction anymore (thanks liliscent)).
template <bool ... Bs>
struct multAnd;
template <>
struct multAnd<> : public std::true_type
{ };
template <bool ... Bs>
struct multAnd<true, Bs...> : public multAnd<Bs...>
{ };
template <bool ... Bs>
struct multAnd<false, Bs...> : public std::false_type
{ };
so foo() can be written as
template <typename ... Args>
typename std::enable_if<
multAnd<std::is_same<char const *, Args>::value ...>::value>::type
foo (Args ... args )
{
for (const char* arg : {args...}) {
std::cout << arg << "\n";
}
}
Using C++14, multAnd() can be written as a constexpr function
template <bool ... Bs>
constexpr bool multAnd ()
{
using unused = bool[];
bool ret { true };
(void)unused { true, ret &= Bs ... };
return ret;
}
so foo() become
template <typename ... Args>
std::enable_if_t<multAnd<std::is_same<char const *, Args>::value ...>()>
foo (Args ... args )
{
for (const char* arg : {args...}) {
std::cout << arg << "\n";
}
}
--- EDIT ---
Jarod42 (thanks!) suggest a far better way to develop a multAnd; something as
template <typename T, T ...>
struct int_sequence
{ };
template <bool ... Bs>
struct all_of : public std::is_same<int_sequence<bool, true, Bs...>,
int_sequence<bool, Bs..., true>>
{ };
Starting from C++14 can be used std::integer_sequence instead of it's imitation (int_sequence).
Using C++17 fold expressions on the comma operator, you can simply do the following:
#include <iostream>
#include <string>
#include <utility>
template<typename OneType>
void foo_(OneType&& one)
{
std::cout << one;
}
template<typename... ArgTypes>
void foo(ArgTypes&&... arguments)
{
(foo_(std::forward<ArgTypes>(arguments)), ...);
}
int main()
{
foo(42, 43., "Hello", std::string("Bla"));
}
Live demo here. Note I used foo_ inside the template, because I couldn't be bothered to write out 4 overloads.
If you really really really want to restrict this to string literals, change the function signature as Nevin's answer suggests:
#include <cstddef>
#include <iostream>
#include <string>
#include <utility>
template<std::size_t N>
using string_literal = const char(&)[N];
template<std::size_t N>
void foo(string_literal<N> literal)
{
std::cout << literal;
}
template<std::size_t... Ns>
void foo(string_literal<Ns>... arguments)
{
(foo(arguments), ...);
}
int main()
{
foo("Hello", "Bla", "haha");
}
Live demo here.
Note this is extremely close to the C++11 syntax to achieve the exact same thing. See e.g. this question of mine.
Well, the nearest you can get to a function accepting any arbitrary number of const char* but nothing else uses a template-function and forwarding:
void foo_impl(std::initializer_list<const char*> args)
{
...
}
template <class... ARGS>
auto foo(ARGS&&... args)
-> foo_impl({std::forward<ARGS>(args)...})
{
foo_impl({std::forward<ARGS>(args)...});
}
The subtlety is in allowing the normal implicit conversions.
#include<type_traits>
#include<iostream>
auto function = [](auto... cstrings) {
static_assert((std::is_same_v<decltype(cstrings), const char*> && ...));
for (const char* string: {cstrings...}) {
std::cout << string << std::endl;
}
};
int main(){
const char b[]= "b2";
const char* d = "d4";
function("a1", b, "c3", d);
//function(a, "b", "c",42); // ERROR
}
And now... for something completely different...
You can write a type wrapper struct as follows
template <typename, typename T>
struct wrp
{ using type = T; };
template <typename U, typename T>
using wrp_t = typename wrp<U, T>::type;
and a foo() function receiving a variadic list of char const * simply become
template <typename ... Args>
void foo (wrp_t<Args, char const *> ... args)
{
for ( char const * arg : {args...} )
std::cout << "- " << arg << std::endl;
}
The problem is that you can't call it as you want
foo("hello", "world");
because the compiler isn't able to deduce the Args... types.
Obviously you can explicit a list of dummy types
foo<void, void>("hello", "world");
but I understand that is a horrible solution.
Anyway, if you accept to pass through a trivial template function
template <typename ... Args>
void bar (Args ... args)
{ foo<Args...>(args...); }
you can call
bar("hello", "world");
The following is a full C++11 working example
#include <iostream>
template <typename, typename T>
struct wrp
{ using type = T; };
template <typename U, typename T>
using wrp_t = typename wrp<U, T>::type;
template <typename ... Args>
void foo (wrp_t<Args, char const *> ... args)
{
for ( char const * arg : {args...} )
std::cout << "- " << arg << std::endl;
}
template <typename ... Args>
void bar (Args ... args)
{ foo<Args...>(args...); }
int main ()
{
bar("hello", "world"); // compile
// bar("hello", "world", 0); // compilation error
}
Of course it is possible, this compiles and runs what you want (pay attention)
#include<iostream>
template<class... Char>
// hehe, here is the secret
auto foo(const Char*... args ) ->decltype((char const*)(*std::begin({args...})), (char const*)(*std::end({args...})), void(0))
{
for (const char* arg : {args...}) {
std::cout << arg << "\n";
}
}
int main() {
foo("no", "sense","of","humor");
}
This is #liliscent solution but with more sugar and, to please #rubenvb, without enable_if.
If you think the extra code as a comment (which is not), note that you'll see exactly the syntax you are looking for.
Note that you can only feed an homogeneous list of things that is convertible to char const*, which was one of your goals it seems.

Function template specialization type - is it optional?

Is the <const char*> optional in below code? I found that g++ and clang compiles without it just fine.
template<typename T>
void debugRep2(T const& t) {
std::cout << "debugRep(const T& t)\n";
}
template<>
void debugRep2<const char*>(const char* const& t) {
//^^^^^^^^^^^^^
std::cout << "const char*& t\n";
}
int main() {
int n;
int *pn = &n;
debugRep2(n);
debugRep2(pn);
}
The templated type is already specified at the function parameter and can be deduced by the compiler
template<>
void debugRep2<const char*>(const char* const& t) {
// ^^^^^^^^^^^ already present
// ...
}
So yes, in this case it is optional.
In fact the common way to write that specialization would be
template<>
void debugRep2(const char* const& t) {
// ...
}

array decay to pointer and overload resolution

I want to be able to differentiate array from pointers in overload resolution :
class string {
public:
string(const char* c_str);
template<int N>
string(const char (&str) [N]);
};
int main() {
const char* c_str = "foo";
string foo(c_str); // ok will call string(const char*)
string bar("bar"); // call string(const char*) instead of the array version
}
The best I have found so far is to use a reference to the pointer instead of a pointer :
class string {
public:
string(const char*& c_str);
template<int N>
string(const char (&str) [N]);
};
int main() {
const char* c_str = "foo";
string foo(c_str); // ok will call string(const char*)
string bar("bar"); // ok, will call the array version
}
it's not exactly the same thing and I want to know if a better way exist
You need to make the first overload a poorer choice when both are viable. Currently they are a tie on conversion ranking (both are "Exact Match"), and then the tie is broken because non-templates are preferred.
This ought to make the conversion ranking worse:
struct stg
{
struct cvt { const char* p; cvt(const char* p_p) : p(p_p) {} };
// matches const char*, but disfavored in overload ranking
stg(cvt c_str); // use c_str.p inside :( Or add an implicit conversion
template<int N>
stg(const char (&str) [N]);
};
You can use SFINAE. This might not be the best way, but it should work ok:
//thanks to dyp for further reduction
template<typename T, typename = typename std::enable_if<std::is_same<T, char>::value>::type>
string(const T * const &) {std::cout << "const char *\n";}
template<std::size_t N> //credit to jrok for noticing the unnecessary SFINAE
string(const char(&)[N]) {std::cout << "const char(&)[" << N << "]\n";}
Here's a live example.
A more generic version of this problem could be detected as follows.
template <class T>
void func(T,
typename std::enable_if<std::is_pointer<T>::value, void>::type * = 0)
{
// catch ptr
}
template <class T, int N>
void func(T (&)[N])
{
//catch array
}
int main(void)
{
int arr[5];
char *b = 0;
func(arr); // catch array
func(b); // catch ptr
}