Template conversion operator issue - c++

I have done a bit of browsing and this was the most relevant link that I could find, however it does not answer my question
Question: Why does the template substitution fail and the following does not compile?
template <typename T>
struct A
{
A() {};
A(T value) : val(value){}
operator T() { return this->val;}
T val;
};
A<std::string> test;
std::cout << "xxx" + std::string(test); //works fine
std::cout << "xxx" + test; //compiler error
Error message:
error: no match for 'operator+' (operand types are 'const char [4]' and 'A<std::__cxx11::basic_string<char> >')
19 | std::cout << "xxx" + test;
| ~~~~~ ^ ~~~~
| | |
| | A<std::__cxx11::basic_string<char> >
| const char [4]

std::operator+(std::basic_string) is a set of operator templates, template argument deduction needs to be performed on the 2nd operand test. But implicit conversion (from A<std::string> to std::string) won't be considered in template argument deduction.
Type deduction does not consider implicit conversions (other than type adjustments listed above): that's the job for overload resolution, which happens later.
As you have showed, explicit conversion like "xxx" + std::string(test); works fine. You can also specify template arguments explicitly (in ugly way) to bypass template argument deduction.
operator+ <char, std::char_traits<char>, std::allocator<char>>("xxx", test);

Related

Conversion from `const char[]` to non-scalar type requested

I'm trying to make a class that wraps a pointer around another type. With all of the extraneous bits removed, it looks like this:
template<typename T>
class field {
std::unique_ptr<T> t_;
public:
field() : t_(nullptr) {}
field(const T &t) : t_(std::make_unique<T>(t)) {}
field<T> &operator=(const T &t) {
t_.reset(new T(t));
return *this;
}
};
I can declare them by explicitly calling their constructors, but not with =, like so:
int main() {
field<std::string> strA("Hello");
field<std::string> strB = "Hello";
return 0;
}
I get the error
-snip-/StringImplicit/main.cpp: In function ‘int main()’:
-snip-/StringImplicit/main.cpp:21:31: error: conversion from ‘const char [6]’ to non-scalar type ‘field<std::__cxx11::basic_string<char> >’ requested
21 | field<std::string> strB = "Hello";
| ^~~~~~~
Where am I going wrong? I can't seem to use field<std::string>s in class constructors with raw strings without this conversion either, it throws the same error.
Edit: The end goal is something like discordpp::ApplicationCommandOption option{.type = 3, .name = "message", .description = "The message to echo", .required = true}; where all of those parameters are differently-typed fields.
The problem is that two class-type conversions are required:
const char[6] to std::string
std::string to field<std::string>.
There is a rule that implicit conversion can have at most one class-type conversion (the official term is "user-defined" conversion although this includes class types that are part of the standard library).
To fix it you can either use your suggested fix; or manually specify one of the conversions, e.g:
auto strB = field<std::string>("Hello");
field<std::string> strC = std::string("Hello");
field<std::string> strD = "Hello"s;
or you could add a constructor for const char[].
SFINAE version of char array constructor (there will be better ways in C++20 I'm sure):
template<size_t N, typename = std::enable_if_t<std::is_same_v<T, std::string>>>
field(char const (&t)[N])
: t_(std::make_unique<T>(t)) {}

std::enable_if to conditionally disable a template constructor

I am trying to get a simple example to work to understand how to use std::enable_if, here is the problem:
I am reading the textbook C++ Templates The Complete Guide by David Vandevoorde, Nicolai M.Josuttis, Chapter 6, Section 5.
This chapter mentions: "std::enable_if to prevent being able to copy objects of a class template C<> if the template parameter is an integral type", and its following code:
template <typename T> class C {
public:
// user-define the predefined copy constructor as deleted (with conversion to
// volatile to enable better matches)
C(C const volatile &) = delete;
// if T is not integral type, provide copy constructor template with better match:
template <typename U,
typename = std::enable_if_t<!std::is_integral<U>::value>>
C(C<U> const &) {
std::cout << "tmpl copy constructor" << std::endl;
}
};
My question is, how should the above code be called and used?
for example, I tried:
C<int> c_int;
std::string s = "sname";
C<std::string> c_string1(std::string);
C<std::string> c_string2(c_string1);
But give me compile error:
specialmember3.cc:22:39: error: no matching function for call to ‘C<std::__cxx11::basic_string<char> >::C(C<std::__cxx11::basic_string<char> > (&)(std::string))’
22 | C<std::string> c_string2(c_string1);
| ^
specialmember3.cc:14:3: note: candidate: ‘template<class U, class> C<T>::C(const C<U>&)’
14 | C(C<U> const &) {
| ^
specialmember3.cc:14:3: note: template argument deduction/substitution failed:
specialmember3.cc:22:39: note: mismatched types ‘const C<U>’ and ‘C<std::__cxx11::basic_string<char> >(std::string)’ {aka ‘C<std::__cxx11::basic_string<char> >(std::__cxx11::basic_string<char>)’}
22 | C<std::string> c_string2(c_string1);
| ^
specialmember3.cc:9:3: note: candidate: ‘constexpr C<T>::C() [with T = std::__cxx11::basic_string<char>]’
9 | C() = default;
| ^
specialmember3.cc:9:3: note: candidate expects 0 arguments, 1 provided
Can someone please give me some hints or code guidance on how to use above template constructor?
You have declared c_string1 as a function. I think you meant this
C<std::string> c_string1;
C<std::string> c_string2(c_string1);
That class, as it is defined, is somewhat useless.
a) You can't default-initialize it, default constructor is removed. C<int> c_int; is ill-formed.
b) You can't create it from value, e.g. C<std::string> c_string1(some_str);, because that constructor does not exist.
Essentially you can copy it, but you cannot create it, which is a nonsense. It breaks rule of 3/5/0.
template <typename T>
class C {
public:
C() /*Initialization here */ {}
C(const T& val) /*Initialization here */ {
std::cout << "tmpl copy constructor: " << val << std::endl;
}
// user-define the predefined copy constructor as deleted (with conversion to
// volatile to enable better matches)
C(C const volatile &) = delete;
// if T is not integral type, provide copy constructor template with better match:
template <typename U,
typename = std::enable_if_t<!std::is_integral<U>::value>>
C(C<U> const &) {
std::cout << "tmpl copy constructor" << std::endl;
}
};
In that case those would be legal:
C<int> c_int;
std::string s = "sname";
C<std::string> c_string1(s);
C<std::string> c_string2(c_string1);
THe copy template defined disallows copying of C if T is integral, so
C<int> c_int2 (c_int); // use of deleted function 'C<T>::C(const volatile C<T>&)

Why can't the compiler use the std::string conversion function of the class when perform operator<<?

Consider the following struct with a user-defined conversion function that can convert itself to const char*;
struct S {
operator const char*() { return "hello"; }
};
This work with <iostream>, we can print the struct S with no error message:
std::cout << S{} << '\n';
But if I change the return type to std::string:
struct S {
operator std::string() { return "hello"; }
};
I got this compiler error message:
<source>:11:13: error: no match for 'operator<<' (operand types are 'std::ostream' {aka 'std::basic_ostream<char>'} and 'S')
11 | std::cout << S{} << '\n';
| ~~~~~~~~~ ^~ ~~~
| | |
| | S
| std::ostream {aka std::basic_ostream<char>}
<source>:11:18: note: 'S' is not derived from 'const std::__cxx11::basic_string<_CharT, _Traits, _Allocator>'
11 | std::cout << S{} << '\n';
| ^
Why can't the compiler use the std::string conversion? Is there a difference between the conversion function of the built-in and class type?
Because operator<< for std::basic_string is a template taking 3 template parameters:
template <class CharT, class Traits, class Allocator>
std::basic_ostream<CharT, Traits>&
operator<<(std::basic_ostream<CharT, Traits>& os,
const std::basic_string<CharT, Traits, Allocator>& str);
And implicit conversion won't be considered in template argument deduction:
Type deduction does not consider implicit conversions (other than type adjustments listed above): that's the job for overload resolution, which happens later.
Then given std::cout << S{};, the template parameter CharT, Traits and Allocator can't be deduced on the 2nd function argument.
On the other hand, operator<< for const char* doesn't have such issue; given std::cout << S{};, the template parameter CharT and Traits would be deduced only from the 1st function argument. After deduction, the implicit conversion from S to const char* will be performed and the calling works well.

User-defined conversion operator template and built-in operators: no match for operator

Consider the following MCVE.
#include <type_traits>
struct A {
template<typename T, typename std::enable_if<std::is_same<T,int>::value,int>::type = 0>
operator T() const { return static_cast<T>(1); }
};
int main() {
int x = 1;
A a;
return x + a;
}
clang compiles it fine. DEMO
But GCC fails with:
error: no match for 'operator+' (operand types are 'int' and 'A')
return x + a;
~~^~~
Question: who is right and why?
I believe clang is right.
To do lookup on +, since at least one argument has class type, we consider member, non-member, and builtin candidates. There aren't any member or non-member candidates, so that's eay enough. There is a builtin candidate for int operator+(int, int), which is the only candidate. That candidate is viable because A can be convertible to int, directly (we have a standard conversion from A to const A& for the implicit object parameter, and then the user defined conversion from that to int, there's no further conversion necessary). As we have one viable candidate, that trivially makes it the best viable candidate.
Note that if A just had operator int() const { return 1; }, gcc would accept it. It's just the conversion function template that fails to be considered.

Why this template parameters con­straint doesn't work?

Reading templates-revisited:
struct S(T : T*) {
T t; // t is supposed to be of type 'int*', but it's of type 'int', why?
}
void main() {
int x = 123;
S!(int*) s;
static assert(is(typeof(s.t) == typeof(&x)));
}
The above code doesn't compile.
Strangely enough, the following does compile:
struct S(T : int*) {
T t;
}
void main() {
int x = 123;
S!(int*) s;
static assert(is(typeof(s.t) == typeof(&x)));
}
I don't understand this behavior. An explanation would be appreciated.
When a type specialization (the type after the colon) is dependent on the parameter identifier, such as T : T*, the resulting identifier refers to the role of the identifier (T) in the type specialization (the deduced type) if there was a match.
Otherwise, if the specialization is independent, such as T : int*, the resulting identifier is an alias of the type specialization.
Examples:
=========================================================
Argument T | Specialization | Result
=========================================================
void | T : void | void
char | T : void | <no match>
int* | T : T* | int
immutable(char)[] | T : T[] | immutable(char)
immutable(char)[] | T : immutable(T)[] | char
=========================================================
When there is a mismatch for an argument passed to a template parameter, the template is dropped from the overload set. An error is raised if the overload set becomes empty before a match is found.
When there is a mismatch in an IsExpression (the is(...) primary expression), the result is false and no symbols are introduced into scope.
As explained in the Argument Deduction section of http://dlang.org/template.html, when deducing the types of template arguments:
If there is no type spe­cial­iza­tion for the pa­ra­me­ter, the type of the pa­ra­me­ter is set to the tem­plate ar­gu­ment.
If the type spe­cial­iza­tion is de­pen­dent on a type pa­ra­me­ter, the type of that pa­ra­me­ter is set to be the
cor­re­spond­ing part of the type ar­gu­ment.
If after all the type ar­gu­ments are ex­am­ined there are any type pa­ra­me­ters left with no type as­signed, they are as­signed types
cor­re­spond­ing to the tem­plate ar­gu­ment in the same po­si­tion in
the Tem­plateAr­gu­mentList.
If ap­ply­ing the above rules does not re­sult in ex­actly one type for each tem­plate pa­ra­me­ter, then it is an error.
And the example that corresponds to your case is:
template TBar(T : T*) { }
alias TBar!(char*) Foo3; // (2) T is deduced to be char
So, what you're seeing in your first example is expected behavior. Because the T is on both sides, T ends up being evaluated to what would result in the template argument being T*. So, since the template argument is int*, T* would be int*, and T ends up being int. What you have is very similar to std.traits.pointerTarget:
/**
Returns the target type of a pointer.
*/
template pointerTarget(T : T*)
{
alias T pointerTarget;
}
Your second example compiles, because that template is requiring that T be implicitly convertible to int*. And since int* is implicitly convertible to itself, when you pass int* as the template argument, it works. What's causing you trouble is when T is on both sides, because then the right-hand side of the expression is dependent on the left.
Now, I assume that what you actually intend to test here is that the template argument is a pointer? If that's the case, then you should use std.traits.isPointer:
struct S(T)
if(isPointer!T)
{
T t;
}