I tried to implement a little example of user-defined type conversion involving templates.
#include <cassert>
#include <cstdint>
#include <iostream>
#include <stdexcept>
#include <type_traits>
template <typename T>
concept bool UIntegral = requires() {
std::is_integral_v<T> && !std::is_signed_v<T>;
};
class Number
{
public:
Number(uint32_t number): _number(number)
{
if (number == 1) {
number = 0;
}
for (; number > 1; number /= 10);
if (number == 0) {
throw std::logic_error("scale must be a factor of 10");
}
}
template <UIntegral T>
operator T() const
{
return static_cast<T>(this->_number);
}
private:
uint32_t _number;
};
void changeScale(uint32_t& magnitude, Number scale)
{
//magnitude *= scale.operator uint32_t();
magnitude *= scale;
}
int main()
{
uint32_t something = 5;
changeScale(something, 100);
std::cout << something << std::endl;
return 0;
}
I get the following compilation error (from GCC 7.3.0):
main.cpp: In function ‘void changeScale(uint32_t&, Number)’:
main.cpp:40:15: error: no match for ‘operator*=’ (operand types are ‘uint32_t {aka unsigned int}’ and ‘Number’)
magnitude *= scale;
Notice the line commented out - this one works:
//magnitude *= scale.operator uint32_t();
Why can't the templated conversion operator be automatically deduced? Thanks in advance for help.
[EDIT]
I followed the advice of removing concepts to use Clang and see its error messages. I got the following (this is truncated but sufficient):
main.cpp:34:15: error: use of overloaded operator '*=' is ambiguous (with operand types 'uint32_t'
(aka 'unsigned int') and 'Number')
magnitude *= scale;
~~~~~~~~~ ^ ~~~~~
main.cpp:34:15: note: built-in candidate operator*=(unsigned int &, float)
main.cpp:34:15: note: built-in candidate operator*=(unsigned int &, double)
main.cpp:34:15: note: built-in candidate operator*=(unsigned int &, long double)
main.cpp:34:15: note: built-in candidate operator*=(unsigned int &, __float128)
main.cpp:34:15: note: built-in candidate operator*=(unsigned int &, int)
main.cpp:34:15: note: built-in candidate operator*=(unsigned int &, long)
main.cpp:34:15: note: built-in candidate operator*=(unsigned int &, long long)
main.cpp:34:15: note: built-in candidate operator*=(unsigned int &, __int128)
main.cpp:34:15: note: built-in candidate operator*=(unsigned int &, unsigned int)
main.cpp:34:15: note: built-in candidate operator*=(unsigned int &, unsigned long)
main.cpp:34:15: note: built-in candidate operator*=(unsigned int &, unsigned long long)
main.cpp:34:15: note: built-in candidate operator*=(unsigned int &, unsigned __int128)
So, having the concepts turned on I assume that the only way to cast a Number is to do it to an unsigned integral type - then why is it insufficient for the compiler to deduce the conversion?
The requires concept expression works like SFINAE, it only checks that the expression is valid, but does not evaluate it.
To have the concept actually restrict T to an unsigned integral type, use a bool expression:
template<typename T>
concept bool UIntegral = std::is_integral_v<T> && !std::is_signed_v<T>;
Will that fix your issue though? Unfortunately not, read on...
Why can't the templated conversion operator be automatically deduced?
Writing buggy C++ code is a sure way to hit a compiler bug :-) There are over 1,000 confirmed unresolved bugs in gcc.
Yes the templated conversion operator should be found, and the "no match for 'operator*='" error message should be instead "ambiguous overload for 'operator*='".
So, having the concepts turned on I assume that the only way to cast a Number is to do it to an unsigned integral type - then why is it insufficient for the compiler to deduce the conversion?
Even if the concept requirement and the compiler bug were fixed, the ambiguity will remain, specifically these four:
main.cpp:34:15: note: built-in candidate operator*=(unsigned int &, unsigned int)
main.cpp:34:15: note: built-in candidate operator*=(unsigned int &, unsigned long)
main.cpp:34:15: note: built-in candidate operator*=(unsigned int &, unsigned long long)
main.cpp:34:15: note: built-in candidate operator*=(unsigned int &, unsigned __int128)
That's because there are a lot of built-in operators for every conceivable promoted built-in type, and int, long, long long and __int128 are all integral types.
For that reason it's usually not a good idea to templatize a conversion to a built-in type.
Solution 1. Make the conversion operator template explicit and request the conversion explicitly
magnitude *= static_cast<uint32_t>(scale);
// or
magnitude *= static_cast<decltype(magnitude)>(scale);
Solution 2. Just implement a non-templated conversion to the type of _number:
struct Number
{
using NumberType = uint32_t;
operator NumberType () const
{
return this->_number;
}
NumberType _number;
};
Related
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.
#include <iostream>
int main(){
int a = 1;
long long b = 2;
std::cout<<(a<b);
std::cout<<std::min(a, b);
return 0;
}
> In file included from /usr/include/c++/4.8/bits/char_traits.h:39:0,
> from /usr/include/c++/4.8/ios:40,
> from /usr/include/c++/4.8/ostream:38,
> from /usr/include/c++/4.8/iostream:39,
> from sum_to.cpp:1: /usr/include/c++/4.8/bits/stl_algobase.h:239:5: note: template<class
> _Tp, class _Compare> const _Tp& std::min(const _Tp&, const _Tp&, _Compare)
> min(const _Tp& __a, const _Tp& __b, _Compare __comp)
> ^ /usr/include/c++/4.8/bits/stl_algobase.h:239:5: note: template argument deduction/substitution failed: sum_to.cpp:7:29:
> note: deduced conflicting types for parameter ‘const _Tp’ (‘int’ and
> ‘long long int’)
> std::cout<<std::min(a, b);
---
Thanks to chris comment in function overloading post
Template argument deduction doesn't take conversions into account. One
template parameter can't match two types
So std::min fail.
Why < would work?
Because built-in < applies Numeric promotions, and template argument deduction doesn't.
As explained in other answers, the reason is that std::min requires the types of the arguments to be identical if deduction is to be performed, while < implies the usual arithmetic conversions (§5.9/2), which will make sure that the types are converted to a "common denominator". Note how §13.6/12 lists up built-in operators as candidates:
For every pair of promoted arithmetic types L and R, there exist
candidate operator functions of the form
// […]
LR operator<(L , R );
// […]
where LR is the result of the usual arithmetic conversions between
types L and R.
Actually, std::min should be able to deal with distinct types. The following is a more modern approach:
template <typename T>
constexpr decltype(auto) min(T&& t) {return std::forward<T>(t);}
template <typename T, typename U, typename... Args>
constexpr auto min(T&& t, U&&u, Args&&... args) {
std::common_type_t<T, U> const& _t(std::forward<T>(t)), _u(std::forward<U>(u));
return min(_t<_u? _t : _u, std::forward<Args>(args)...);
}
Demo.
It is because std::min is a template function.
template <class T> const T& min (const T& a, const T& b) {
return !(b<a)?a:b; // or: return !comp(b,a)?a:b; for version (2)
}
so it needs the arguments to have the same type, but if you use (a<b), so a could implicitly converted to a long long
The < operator is binary, so the compiler could convert arguments to the same type and compare them.
Otherwise min function should return something. How could compiler guess which type should he return?
Primitive types don't overload operators, so usual arithmetic conversions are applied and your int is converted to a long long, and the "<" has a valid meaning.
You can't even overload operators for primitive types:
https://isocpp.org/wiki/faq/intrinsic-types#intrinsics-and-operator-overloading
Example to show that your int is promoted to long long
// common_type example
#include <iostream>
#include <type_traits>
int main() {
typedef std::common_type<int, long long>::type A; // i
std::cout << "A: " << std::is_same<long long,A>::value << std::endl;
return 0;
}
Documentation
http://en.cppreference.com/w/cpp/language/operator_arithmetic
For the binary operators (except shifts), if the promoted operands
have different types, additional set of implicit conversions is
applied, known as usual arithmetic conversions with the goal to
produce the common type (also accessible via the std::common_type type
trait)
I'm creating an unindent algorithm for a text-editor. I've managed to obtain the range to operate on, but when I want to do the Gtk::TextBuffer::erase, it fails:
void unindentSelection(const Glib::RefPtr<Gtk::TextBuffer> &buffer)
{
Gtk::TextBuffer::iterator start, end;
buffer->get_selection_bounds(start, end);
auto selRange = boost::make_iterator_range(start, end);
auto buffRange = boost::make_iterator_range(buffer->begin(), buffer->end());
auto prevRangeRev = boost::make_iterator_range(buffRange.begin(), selRange.begin()) | boost::adaptors::reversed;
auto prevRangeLineRev = boost::find<boost::return_begin_found>(prevRangeRev, '\n');
auto prevRangeLine = prevRangeLineRev | boost::adaptors::reversed;
auto afterRange = boost::make_iterator_range(selRange.end(), buffRange.end());
auto afterRangeLine = boost::find<boost::return_begin_found>(afterRange, '\n');
auto exSelRangeAux = boost::join(prevRangeLine, selRange);
auto exSelRange = boost::join(exSelRangeAux, afterRangeLine);
show_range(exSelRange);
while (true)
{
auto spaceRange = boost::find_if<boost::return_begin_found>(exSelRange, findNonspaceNL);
if (boost::distance(spaceRange))
{
buffer->erase(spaceRange.begin(), spaceRange.end());
}
}
}
TextEditor.cpp:501:31: error: no viable conversion from 'boost::range_detail::join_iterator >, Gtk::TextIter, unsigned int, unsigned int, boost::iterators::bidirectional_traversal_tag>, Gtk::TextIter, unsigned int, unsigned int, boost::iterators::bidirectional_traversal_tag>' to 'const iterator' (aka 'const Gtk::TextIter')
buffer->erase(spaceRange.begin(), afterRangeLine.end());
^~~~~~~~~~~~~~~~~~
/usr/include/gtkmm-3.0/gtkmm/textiter.h:145:7: note: candidate constructor (the implicit copy constructor) not viable: no known conversion from 'boost::range_detail::join_iterator >, Gtk::TextIter, unsigned int, unsigned int, boost::iterators::bidirectional_traversal_tag>, Gtk::TextIter, unsigned int, unsigned int, boost::iterators::bidirectional_traversal_tag>' to 'const Gtk::TextIter &' for 1st argument
class TextIter
^
/usr/include/gtkmm-3.0/gtkmm/textiter.h:145:7: note: candidate constructor (the implicit move constructor) not viable: no known conversion from 'boost::range_detail::join_iterator >, Gtk::TextIter, unsigned int, unsigned int, boost::iterators::bidirectional_traversal_tag>, Gtk::TextIter, unsigned int, unsigned int, boost::iterators::bidirectional_traversal_tag>' to 'Gtk::TextIter &&' for 1st argument
class TextIter
^
/usr/include/gtkmm-3.0/gtkmm/textbuffer.h:378:34: note: passing argument to parameter 'range_begin' here
iterator erase(const iterator& range_begin, const iterator& range_end);
Any ideas?
I had to create an iterator type that contained a templated 'Iterator it' inside (which in my case is the Gtk::TextIter), using 'boost::iterator_facade'. All operations to the iterator where forwarded to 'it', except the operator*(), that just gave the plain iterator. So the erasing was done in this way:
buffer->erase(*spaceRange.begin(), *spaceRange.end());
This new type of iterator will be very useful manipulating ranges. Not sure this is the best solution, but works.
I have this simplified code consisting of a class with a static function, which is stored in map:
#include <iostream>
#include <functional>
#include <map>
class A {
public:
static void f(const std::string &s) { std::cout << s; }
};
std::map<std::string, std::function<void(std::string const &)>> fs;
int main() {
fs["f"] = &A::f;
fs["f"]("hello");
}
This prints the expected hello.
The problem occurs if I overload f() with:
static void f(const std::string &s, int c) { while(c-->0) { std::cout << s; } }
This results in the error:
error: no viable overloaded '='
fs["f"] = &A::f;
~~~~~~~ ^ ~~~~~
/usr/bin/../lib/gcc/x86_64-linux-gnu/4.9/../../../../include/c++/4.9/functional:2241:7: note: candidate function not viable: no overload of 'f' matching 'const std::function<void (const std::basic_string<char> &)>' for 1st argument
operator=(const function& __x)
^
/usr/bin/../lib/gcc/x86_64-linux-gnu/4.9/../../../../include/c++/4.9/functional:2259:7: note: candidate function not viable: no overload of 'f' matching 'std::function<void (const std::basic_string<char> &)>' for 1st argument
operator=(function&& __x)
^
/usr/bin/../lib/gcc/x86_64-linux-gnu/4.9/../../../../include/c++/4.9/functional:2273:7: note: candidate function not viable: no overload of 'f' matching 'nullptr_t' for 1st argument
operator=(nullptr_t)
^
/usr/bin/../lib/gcc/x86_64-linux-gnu/4.9/../../../../include/c++/4.9/functional:2302:2: note: candidate template ignored: couldn't infer template argument '_Functor'
operator=(_Functor&& __f)
^
/usr/bin/../lib/gcc/x86_64-linux-gnu/4.9/../../../../include/c++/4.9/functional:2311:2: note: candidate template ignored: couldn't infer template argument '_Functor'
operator=(reference_wrapper<_Functor> __f) noexcept
^
However, calling both functions works:
A::f("hello ");
A::f("echo ", 3);
So, my question are:
Why this code not compiling even though the operator= seems to exist and function if I don't overload f()?
How can I get it to work without giving both functions different names?
Why this code not compiling even though the operator= seems to exist
and function if I don't overload f()?
Because the compiler doesn't know which overload to choose. How could he? There is no criterion upon which he can decide which one is suited better. Every std::function allows arbitrary function objects to be assigned and doesn't check any signatures. If you wanted to save only function pointers of this particular signature you should have declared the map appropriately.
How can I get it to work without giving both functions different
names?
As already mentioned it works by casting the expression to a function pointer of the specific type.
fs["f"] = static_cast<void(*)(std::string const&)>( &A::f );
This way no ambiguities arise; There is exactly one overload that can be casted to this function to pointer type. If this appears more often then a typedef could be feasible.
Or a little helper class template:
template <typename... Exact>
struct funptr
{
template <typename R>
constexpr auto operator()(R(*p)(Exact...)) -> decltype(p)
{ return p; }
};
fs["f"] = funptr<std::string const&>()(&A::f);
Demo.
While porting Windows code to Linux, I encountered the following error message with GCC 4.2.3. (Yes, I'm aware that it's a slight old version, but I can't easily upgrade.)
main.cpp:16: error: call of overloaded ‘list(MyClass&)’ is ambiguous
/usr/include/c++/4.2/bits/stl_list.h:495: note: candidates are: std::list<_Tp, _Alloc>::list(const std::list<_Tp, _Alloc>&) [with _Tp = unsigned char, _Alloc = std::allocator<unsigned char>]
/usr/include/c++/4.2/bits/stl_list.h:484: note: std::list<_Tp, _Alloc>::list(size_t, const _Tp&, const _Alloc&) [with _Tp = unsigned char, _Alloc = std::allocator<unsigned char>]
I'm using the following code to generate this error.
#include <list>
class MyClass
{
public:
MyClass(){}
operator std::list<unsigned char>() const { std::list<unsigned char> a; return a; }
operator unsigned char() const { unsigned char a; return a; }
};
int main()
{
MyClass a;
std::list<unsigned char> b = (std::list<unsigned char>)a;
return 0;
}
Has anyone experienced this error? More importantly, how to get around it? (It's possible to completely avoid the overload, sure, by using functions such as GetChar(), GetList() etc, but I'd like to avoid that.)
(By the way, removing "operator unsigned char()" removes the error.)
The ambiguity comes from the interpretation of the cast-expression.
When choosing the conversion, the compiler first considers a static_cast style cast and considers how to resolve an initialization which looks like this:
std::list<unsigned_char> tmp( a );
This construction is ambiguous as a has a user-defined conversion to a std::list<unsigned char> and to an unsigned char and std::list<unsigned char> has both a constructor which takes a const std::list<unsigned char>& and a constructor which takes size_t (to which an unsigned char can be promoted).
When casting to a const std::list<unsigned_char>&, this initialization is considered:
const std::list<unsigned_char>& tmp( a );
In this case, when the user-defined conversion to std::list<unsigned_char> is chosen, the new reference can bind directly to the result of the conversion. If the user-defined conversion to unsigned char where chosen a temporary object of type std::list<unsigned char> would have to be created and this makes this option a worse conversion sequence than the former option.
I've simplified your example to the following:
typedef unsigned int size_t;
template <typename T>
class List
{
public:
typedef size_t size_type;
List (List const &);
List (size_type i, T const & = T());
};
typedef List<unsigned char> UCList;
class MyClass
{
public:
operator UCList const () const;
operator unsigned char () const;
};
void foo ()
{
MyClass mc;
(UCList)mc;
}
The first point, is that the standard defines that the C-style cast should use the more appropriate C++ style cast, and in this case that's static_cast. So the above cast is equivalent to:
static_cast<UCList> (mc);
The definition of static_cast says:
An expression e can be explicitly converted to a type T using a static_cast of the form
static_cast<T>(e) if the declaration "T t(e);" is well-formed, for some invented temporary variable
t (8.5)
So the semantics for the cast are the same as for:
UCList tmp (mc);
From 13.3.1.3 we get the set of candidate constructors that we can use in UCList:
UCList (UCList const &) #1
UCList (size_type, T const & = T()); #2
What happens next is two separate overload resolution steps, one for each conversion operator.
Converting to #1: With a target type of UCList const &, overload resolution selects between the following conversion operators.: "operator UCList const ()" and "operator unsigned char ()". Using unsigned char would require an additional user conversion and so is not a viable function for this overload step. Therefore overload resolution succeeds and will use operator UCList const ().
Converting to #2: With a target type of size_t. The default argument does not take part in overload resolution. Overload resolution again selects between the conversion operators: "operator UCList const ()" and "operator unsigned char ()". This time there is no conversion from UCList to unsigned int and so that is not a viable function. An unsigned char can be promoted to size_t and so this time overload resolution succeeds and will use "operator UCList const ()".
But, now back at the top level there are two separate and independent overload resolution steps that have successfully converted from mc to UCList. The result is therefore ambiguous.
To explain that last point, this example is different to the normal overload resolution case. Normally there is a 1:n relationship between argument and parameter types:
void foo (char);
void foo (short);
void foo (int);
void bar() {
int i;
foo (i);
}
Here there is i=>char, i=>short and i=>int. These are compared by overload resolution and the int overload would be selected.
In the above case we have an m:n relationship. The standard outlines the rules to select for each individual argument and all of the 'n' parameters, but that's where it ends, it does not specify how we should decide between using the different 'm' arguments.
Hope this makes some sense!
UPDATE:
The two kinds of initialization syntax here are:
UCList t1 (mc);
UCList t2 = mc;
't1' is a direct initialiation (13.3.1.3) and all constructors are included in the overload set. This is almost like having more than one user defined conversion. There are the set of constructors and the set of conversion operators. (ie. m:n).
In the case of 't2' the syntax uses copy-initialization (13.3.1.4) and the rules different:
Under the conditions specified in 8.5, as part of a copy-initialization of an object of class type, a userdefined
conversion can be invoked to convert an initializer expression to the type of the object being initialized.
Overload resolution is used to select the user-defined conversion to be invoked
In this case there is just one to type, UCList, and so there is only the set of conversion operator overloads to consider, ie. we do not consider the other constructors of UCList.
It compiles properly if you remove the cast, and I've checked that the operator std::list is being executed.
int main()
{
MyClass a;
std::list<unsigned char> b = a;
return 0;
}
Or if you cast it to a const reference.
int main()
{
MyClass a;
std::list<unsigned char> b = (const std::list<unsigned char>&)a;
return 0;
}