SFINAE for unsigned type selection - c++

I'm trying to use SFINAE to check if a type has an unsigned equivalent. While it seems to work for int and bool, it fails for float. From the error it seems a certain type is not defined. The question is if the template argument to enable_if is ill-formed, why isn't this removed from overload selection ?
#include <type_traits>
#include <iostream>
template <typename T>
std::enable_if_t<sizeof(std::make_unsigned<T>), bool> hasUnsigned(T x)
{
return true;
}
bool hasUnsigned(...)
{
return false;
}
int main()
{
float x; // If it's int, or char, it below displays true
std::cout << std::boolalpha << hasUnsigned(x) << std::endl;
}
Error with float
In file included from has_unsigned.cc:1:
/usr/include/c++/10/type_traits: In instantiation of ‘struct std::make_unsigned<float>’:
has_unsigned.cc:5:18: required by substitution of ‘template<class T> std::enable_if_t<(sizeof (std::make_unsigned<_Tp>) != 0), bool> hasUnsigned(T) [with T = float]’
has_unsigned.cc:18:48: required from here
/usr/include/c++/10/type_traits:1826:62: error: invalid use of incomplete type ‘class std::__make_unsigned_selector<float, false, false>’
1826 | { typedef typename __make_unsigned_selector<_Tp>::__type type; };
| ^~~~
/usr/include/c++/10/type_traits:1733:11: note: declaration of ‘class std::__make_unsigned_selector<float, false, false>’
1733 | class __make_unsigned_selector;
| ^~~~~~~~~~~~~~~~~~~~~~~~

You are using make_unsigned on an invalid type (see below) which makes the behavior undefined or program ill-formed. A better approach would be to check if it's an integer:
std::enable_if_t<std::is_integral_v<T>, bool>
From std::make_unsigned:
If T is an integral (except bool) or enumeration type, provides the member typedef type which is the unsigned integer type corresponding to T, with the same cv-qualifiers.
If T is signed or unsigned char, short, int, long, long long; the unsigned type from this list corresponding to T is provided.
If T is an enumeration type or char, wchar_t, char8_t (since C++20), char16_t, char32_t; the unsigned integer type with the smallest rank having the same sizeof as T is provided.
Otherwise, the behavior is undefined. (until C++20)
Otherwise, the program is ill-formed. (since C++20)

Related

enable_if compilation question void = nullptr

Does anyone know why assigning type* = 0 doesn't work, while type* = nullptr does? In both cases typedef void type. Thanks
#include <type_traits>
#include <iostream>
template <class T,
typename std::enable_if<std::is_integral<T>::value>::type* = 0>
void do_stuff(T& t) {
std::cout << "do_stuff integral\n";
}
#if 0 // works
template <class T,
typename std::enable_if<std::is_integral<T>::value>::type* = nullptr>
void do_stuff(T& t) {
std::cout << "do_stuff integral\n";
}
#endif
struct S {};
int main(int argc, char *argv[])
{
int i = 1;
do_stuff(i);
return 0;
}
Compilation:
clang++ -pedantic -Wall -std=c++11 test190.cc && ./a.out
test190.cc:23:5: error: no matching function for call to 'do_stuff'
do_stuff(i);
^~~~~~~~
test190.cc:6:6: note: candidate template ignored: substitution failure
[with T = int]: null non-type template argument must be cast to template
parameter type 'typename
std::enable_if<std::is_integral<int>::value>::type *' (aka 'void *')
void do_stuff(T& t) {
^
1 error generated.
Technically speaking, this is because a non-type template argument must be a "converted constant expression" of the parameter type. This means that the argument itself must be a constant expression, and its conversion to the required parameter type must use only the conversions specified in [expr.const]/4.
According to [expr.const]/4, null pointer conversions are only allowed from std::nullptr_t. In other words, the conversion from 0 to a null pointer value is not allowed as part of the implicit conversion sequence in a converted constant expression.
Yet it's perfectly legitimate to specify static_cast<T*>(0) as a template argument to a non-type template parameter of type T*. In other words, a null pointer conversion from 0 is allowed as part of a constant expression. It's only when the conversion is done at a certain point---after computing the argument and while converting the argument type to the parameter type---that the standard forbids it.
I have no idea about the rationale for this rule.
** nullptr and 0 are not the same. **
For a very clear explanation please see the following:
https://hackernoon.com/what-exactly-is-nullptr-in-c-94d63y6t
#brian has provided a very good technical answer, but I felt it necessary to add this answer since we should no longer be trying to use 0 for pointer values.

Specializing template with integer_sequence. GCC vs MSVC

So, I came across a piece of code that behaves differently in GCC and MSVC:
#include <utility>
typedef int IType;
template<typename> struct A;
template<int... Ns>
struct A<std::integer_sequence<IType, Ns...>> {
using type = bool;
};
using B = typename A<std::make_integer_sequence<IType, 3>>::type;
int main() {
B b;
}
This happily compiles on both compilers. However, if you define IType as typedef long IType; MSVC still works whereas GCC says:
source>:12:61: error: invalid use of incomplete type 'struct A<std::integer_sequence<long int, 0, 1, 2> >'
12 | using B = typename A<std::make_integer_sequence<IType, 3>>::type;
| ^~~~
<source>:5:27: note: declaration of 'struct A<std::integer_sequence<long int, 0, 1, 2> >'
5 | template<typename> struct A;
| ^
<source>: In function 'int main()':
<source>:15:3: error: 'B' was not declared in this scope
15 | B b;
| ^
Compiler returned: 1
So, obviously when IType is long GCC fails to use the 2nd more specialized definition of A and thus fails. I'm really struggling to understand why int and long are treated differently by GCC here.
I used GCC 10.1 and MSVC 19.24 in Compiler Explorer to play with it.
https://godbolt.org/z/7L3xap
std::integer_sequence is defined as
template< class T, T... Ints >
class integer_sequence;
That is, the type of the values is T.
So when changing IType to long, also the type of Ns... should be changed to long...:
typedef int IType;
template<typename> struct A;
template<IType... Ns> // <--- HERE
struct A<std::integer_sequence<IType, Ns...>> {
using type = bool;
};
Otherwise you get a specialization of struct A<long, int, int, int> which won't match struct A<long, long, long, long> (MSVC seems to be more lenient on this, but GCC behavior is more correct IMO).

How to properly replace boost::variant by std::variant?

Using boost:variant:
#include <tuple>
#include <iostream>
#include <boost/variant.hpp>
template <size_t n, typename... T>
boost::variant<T...> _tuple_index(size_t i, const std::tuple<T...>& tpl) {
if (i == n)
return std::get<n>(tpl);
else if (n == sizeof...(T) - 1)
throw std::out_of_range("Out of Index");
else
return _tuple_index<(n < sizeof...(T)-1 ? n+1 : 0)>(i, tpl);
}
template <typename... T>
boost::variant<T...> tuple_index(size_t i, const std::tuple<T...>& tpl) {
return _tuple_index<0>(i, tpl);
}
template <typename T>
auto tuple_len(T &tpl) {
return std::tuple_size<T>::value;
}
int main()
{
std::tuple<std::string, double, double, int> t("123", 4.5, 6.7, 8);
for(int i = 0; i != tuple_len(t); ++i) {
std::cout << tuple_index(i, t) << std::endl; // works with boost
}
}
Replace boost::variant by std::variant, added a helper to stream std::variant:
#include <tuple>
#include <iostream>
#include <variant>
template <size_t n, typename... T>
std::variant<T...> _tuple_index(size_t i, const std::tuple<T...>& tpl) {
if (i == n)
return std::get<n>(tpl);
else if (n == sizeof...(T) - 1)
throw std::out_of_range("Out of Index");
else
return _tuple_index<(n < sizeof...(T)-1 ? n+1 : 0)>(i, tpl);
}
template <typename... T>
std::variant<T...> tuple_index(size_t i, const std::tuple<T...>& tpl) {
return _tuple_index<0>(i, tpl);
}
template <typename T>
auto tuple_len(T &tpl) {
return std::tuple_size<T>::value;
}
// added helper to stream std::variant
template <typename T0, typename ... Ts>
std::ostream & operator<< (std::ostream & s, std::variant<T0, Ts...> const & v) {
std::visit([&](auto && arg){ s << arg;}, v);
return s;
}
int main()
{
std::tuple<std::string, double, double, int> t("123", 4.5, 6.7, 8);
for(int i = 0; i != tuple_len(t); ++i) {
std::cout << tuple_index(i, t) << std::endl; // doesn't work anymore
}
}
The compilation remain error:
$ clang++ -v [17:37:47]
Apple LLVM version 10.0.1 (clang-1001.0.46.4)
Target: x86_64-apple-darwin18.6.0
Thread model: posix
InstalledDir: /Library/Developer/CommandLineTools/usr/bin
$ clang++ -std=c++17 isostd.cpp
isostd.cpp:8:16: error: no viable conversion from returned value of type 'const typename tuple_element<1UL, tuple<basic_string<char>, double, double, int> >::type' (aka 'const __type_pack_element<1UL, std::__1::basic_string<char>, double, double, int>') to function return
type 'std::variant<basic_string<char>, double, double, int>'
return std::get<n>(tpl);
^~~~~~~~~~~~~~~~
isostd.cpp:12:16: note: in instantiation of function template specialization '_tuple_index<1, std::__1::basic_string<char>, double, double, int>' requested here
return _tuple_index<(n < sizeof...(T)-1 ? n+1 : 0)>(i, tpl);
^
isostd.cpp:16:12: note: in instantiation of function template specialization '_tuple_index<0, std::__1::basic_string<char>, double, double, int>' requested here
return _tuple_index<0>(i, tpl);
^
isostd.cpp:35:22: note: in instantiation of function template specialization 'tuple_index<std::__1::basic_string<char>, double, double, int>' requested here
std::cout << tuple_index(i, t) << std::endl; // doesn't work anymore
^
/Library/Developer/CommandLineTools/usr/include/c++/v1/variant:1142:3: note: candidate constructor not viable: no known conversion from 'const typename tuple_element<1UL, tuple<basic_string<char>, double, double, int> >::type'
(aka 'const __type_pack_element<1UL, std::__1::basic_string<char>, double, double, int>') to 'const std::__1::variant<std::__1::basic_string<char>, double, double, int> &' for 1st argument
variant(const variant&) = default;
^
/Library/Developer/CommandLineTools/usr/include/c++/v1/variant:1143:3: note: candidate constructor not viable: no known conversion from 'const typename tuple_element<1UL, tuple<basic_string<char>, double, double, int> >::type'
(aka 'const __type_pack_element<1UL, std::__1::basic_string<char>, double, double, int>') to 'std::__1::variant<std::__1::basic_string<char>, double, double, int> &&' for 1st argument
variant(variant&&) = default;
^
/Library/Developer/CommandLineTools/usr/include/c++/v1/variant:1155:13: note: candidate template ignored: substitution failure [with _Arg = const double &, $1 = 0, $2 = 0, $3 = 0, _Tp = double]: no member named 'value' in
'std::__1::__find_detail::__find_unambiguous_index_sfinae<double, std::__1::basic_string<char>, double, double, int>'
constexpr variant(_Arg&& __arg) noexcept(
^
1 error generated.
How can I properly replace boost:variant by std::variant?
I aware of the reference: What are the differences between std::variant and boost::variant?
Boost.Variant includes recursive_variant, which allows a variant to contain itself. They're essentially special wrappers around a pointer to a boost::variant, but they are tied into the visitation machinery.
If I understand correctly, there is no way to finish the replace?
As you have duplicate types in your variant some of the constructors are disabled:
This overload only participates in overload resolution if there is exactly one occurrence of T in Types...
You need to use the constructor with an explicit type index:
return std::variant<T...>(std::in_place_index<n>, std::get<n>(tpl));
Your original boost code has undefined behaviour:
Each type specified as a template argument to variant must be distinct after removal of qualifiers. Thus, for instance, both variant<int, int> and variant<int, const int> have undefined behavior.
The standard libary implementation does support duplicate types and therefore prevents you from accidentally constructing an ambigous variant. For example what should the following do:
variant<std::string, double, double, int> t = 4.5;
With boost it is UB, either of the double values might be initialised or it might do something completely different. The standard library explitly makes this a compiler error so that you have to choose which of your doubles you want to initialise.

Why less than operator accepts different types of params while std::min not?

#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)

numeric_limits::max and "invalid conversion from 'int (*)()noexcept (true)' to 'value_t {aka int}'"

I have a type defined using typedef unsigned int value_t; and a function
value_t find_minimal_value(...) {
...
if(...) return numeric_limits<value_t>::max;
...
}
Compiler refuses to compile it, saying: invalid conversion from ‘int (*)()noexcept (true)’ to ‘value_t {aka int}’.
What does it mean? Looking into the numeric_limits class, the min() function should return a variable of the type passed to it via template typename, so value_t in this case. So why the code doesn't compile?
std::numeric_limits::max() is a function, so you need to return the result of its invocation. That is done using the call operator:
value_t find_minimal_value() {
if (...) return numeric_limits<value_t>::max();
// ^^
}
The error message meant that it couldn't convert a function pointer (i.e int (*)() to unsigned int.