How to use enable_if correctly? - c++

I need to learn how to use enable_if. For this I need to reimplement the distance function using enable_if. I tried this:
#include <iostream>
#include <vector>
#include <list>
#include <utility>
#include <type_traits>
template<class In>
typename std::enable_if<!std::is_random_acces_iterator<In>::value, std::iterator_traits<In>::difference_type>::type my_distance(In begin, In end, std::input_iterator_tag dummy){
typename std::iterator_traits<In>::difference_type n = 0;
while(begin!=end){
++begin; ++n;
}
std::cout << "STEPPING" << std::endl;
return n;
}
template<class Ran>
typename std::enable_if<std::is_random_acces_iterator<Ran>::value, std::iterator_traits<In>::difference_type>::type my_distance(Ran begin, Ran end, std::random_access_iterator_tag dummy){
std::cout << "RANDOM" << std::endl;
return end - begin;
}
template <class I> inline
typename std::iterator_traits<I>::difference_type my_distance_wrapper(I begin, I end){
typedef typename std::iterator_traits<I>::iterator_category cat;
return my_distance(begin, end, cat());
}
int main() {
std::vector<int> ve;
std::list<int> li;
for(int i = 0; i < 3; i++){
ve.push_back(i);
li.push_back(i);
}
std::cout << my_distance_wrapper(ve.begin(), ve.end()) << std::endl;
std::cout << my_distance_wrapper(li.begin(), li.end()) << std::endl;
return 0;
}
I thought I could do this by using some function like std::is_random_acces_iterator<In>::value similar to std::is_pod<In>::value
I checked the type_traits but could not find anything for checking if something is a specific iterator.
How would I check if something is an random_acces_iterator? I know I could just do this in a function:
template<class T>
bool f(){
typedef typename std::iterator_traits<T>::iterator_category cat;
return std::random_access_iterator_tag == cat();
}
So my question is basically how do I do function f in a template? And no not using enable_if is not possible, it is a requirement of my task. And am I correct to believe that SFINAE would correctly discard the other function, if I could get function f into that template?

You can use the type traits std::is_same<> as follows
template<typename iterator>
using is_random_access_iterator =
std::is_same<typename std::iterator_traits<iterator>::iterator_category,
std::random_access_iterator_tag>;
and then
template<typename iterator>
std::enable_if_t<is_random_access_iterator<iterator>::value,
typename std::iterator_traits<iterator>::difference_type>
my_distance(iterator a, iterator b) { return a-b; }
template<typename iterator>
std::enable_if_t<!is_random_access_iterator<iterator>::value,
typename std::iterator_traits<iterator>::difference_type>
my_distance(iterator a, iterator b) { /* ... */ }
Here, std::enable_if_t<,> is a helper alias defined (since C++14) as
template<bool condition, typename type = void>
using enable_if_t = typename enable_if<condition,type>::type;
Actually, you can also declare your function f() as constexpr and use it directly within the enable_if, i.e. std::enable_if<f<It>(), ...>.

Related

How can I decide if I want to return a pair with an integer key or string key inside a template function

I'm trying to figure out what type the template currently is, I have looked in stackoverflow but did not find a solid answer. Basically I want to create an #if #else #endif and depending on the type of the template put code.
Here is my code
#include <map>
#include <iostream>
template <typename T>
#define IS_INT std::is_integral<T::first_type>::value
T create_a_pair()
{
#if IS_INT
return std::make_pair(5, typename T::second_type()));
#else
return T();
#endif
}
int main()
{
std::cout << create_a_pair<std::pair<int, int> >().first << std::endl;
std::cout << create_a_pair<std::pair<std::string, int> >().first << std::endl;
}
I tried this also but I got a compile time error saying I can't use integer 5 because the type is a string.
#include <map>
#include <iostream>
template <typename T>
T create_a_pair()
{
if (std::is_integral<T::first_type>::value)
return std::make_pair(5, typename T::second_type()));
return T();
}
int main()
{
std::cout << create_a_pair<std::pair<int, int> >().first << std::endl;
std::cout << create_a_pair<std::pair<std::string, int> >().first << std::endl;
}
Whenever I need some type_traits from newer C++ standards, I steal them and adapt them to the older standard (if needed).
Example:
namespace traits98 {
struct false_type { static const bool value; };
const bool false_type::value = false;
struct true_type { static const bool value; };
const bool true_type::value = true;
template<class T, class U> struct is_same : false_type {};
template<class T> struct is_same<T, T> : true_type {};
template<bool B, class T = void> struct enable_if {};
template<class T> struct enable_if<true, T> { typedef T type; };
} // namespace traits98
With those, you can easily make overloads:
#include <iostream>
#include <string>
#include <utility>
using namespace traits98;
template <typename F, typename S>
typename enable_if<!is_same<int, F>::value, std::pair<F,S> >::type
create_a_pair() { return std::pair<F,S>(); }
template <typename F, typename S>
typename enable_if<is_same<int, F>::value, std::pair<F,S> >::type
create_a_pair() {
return std::make_pair(5, S());
}
int main() {
std::cout << create_a_pair<int, int>().first << '\n';
std::cout << create_a_pair<std::string, int>().first << '\n';
}

SFINAE: 'enable_if' cannot be used to disable this declaration

I want to enable and disable a function declaration in a template class, just based on if the template parameter has one type defined or not for which I use boost/tti/has_type.hpp. However, I got the complains from compiler, i.e., 'enable_if' cannot be used to disable this declaration.
#include <boost/tti/has_type.hpp>
#include <iostream>
#include <vector>
#include <set>
using namespace std;
BOOST_TTI_HAS_TYPE(key_type)
template <typename container>
class adapter : public container
{
public:
using container::container;
public:
template <typename type = typename enable_if<!has_type_key_type<container>::value,typename container::value_type>::type>
bool contains(typename container::value_type const & v) { return find(begin(*this),end(*this),v) != end(*this); }
template <typename type = typename enable_if<has_type_key_type<container>::value,typename container::value_type>::type>
bool contains(typename container::key_type const & k) { return this->find(k) != this->end(); }
};
int main()
{
cout << has_type_key_type<adapter<vector<int>>>::value << endl;
cout << has_type_key_type<adapter<set<int>>>::value << endl;
}
How could I resolve it? However, if I change it to similar non-member function template, it works.
#include <boost/tti/has_type.hpp>
#include <iostream>
#include <vector>
#include <set>
using namespace std;
BOOST_TTI_HAS_TYPE(key_type)
template <typename container, typename enable_if<!has_type_key_type<container>::value,int>::type = 0>
bool contains(container const & c, typename container::value_type const & v) { return find(begin(c),end(c),v) != end(c); }
template <typename container, typename enable_if<has_type_key_type<container>::value,int>::type = 0>
bool contains(container const & c, typename container::key_type const & k) { return c.find(k) != c.end(); }
template <typename container>
class adapter : public container
{
public:
using container::container;
public:
// ...
};
int main()
{
vector<double> v{3.14};
set<double> s{2.71};
cout << contains(v,3.14) << endl;
cout << contains(s,2.71) << endl;
}
if I change it to similar non-member function template, it works.
The point is: funtion template.
Your code doesn't works because SFINAE works over templates, with test related to the template parameter. Your contains() method is a function, inside a template class, but isn't a template function.
To make SFINAE works for contains(), you have to transform it in a template function.
You have seen that works outside the class, but works also inside the class.
For example, with the following trick (caution: code not tested)
// .......VVVVVVVVVVVVVVVVVVVVVV
template <typename C = container, // ................V
typename std::enable_if<!has_type_key_type<C>::value, int>::type = 0>
bool contains (typename container::value_type const & v)
{ return find(begin(*this),end(*this),v) != end(*this); }
// .......VVVVVVVVVVVVVVVVVVVVVV
template <typename C = container, // ...............V
typename std::enable_if<has_type_key_type<C>::value, int>::type = 0>
bool contains (typename container::key_type const & k)
{ return this->find(k) != this->end(); }
Observe that the SFINAE test (has_type_key_type<C>::value) now involve C, the function's template parameter, not container, the template parameter of the class.
If you want avoid that constains() can be "hijacked" (explicitly setting a type for C, different from container, you can add a variadic non-type (and unused) template parameter.
For example
// .......VVVVVV
template <int..., typename C = container,
typename std::enable_if<!has_type_key_type<C>::value, int>::type = 0>
bool contains (typename container::value_type const & v)
{ return find(begin(*this),end(*this),v) != end(*this); }
// .......VVVVVV
template <int..., typename C = container,
typename std::enable_if<has_type_key_type<C>::value, int>::type = 0>
bool contains (typename container::key_type const & k)
{ return this->find(k) != this->end(); }
Off Topic: you you can use at least C++14, you ca use std::enable_if_t, so
std::enable_if_t<has_type_key_type<C>::value, int> = 0
instead of
typename std::enable_if<has_type_key_type<C>::value, int>::type = 0

SFINAE applied to iterator

I'm writing a template function that should only accept random access iterator of any container containing a specific type (defined by template ).
At the moment, I'm first trying to limit the type of the iterator using SFINAE but the code does not compile.
#include <iostream>
#include <type_traits>
#include <vector>
template<typename It,
std::enable_if<
std::is_same<typename std::iterator_traits<It>::iterator_category,
std::random_access_iterator_tag>::value,
typename std::iterator_traits<It>::difference_type>>
void func(const It& begin, const It& end)
{
std::cout << begin[0] << std::endl;
}
int main()
{
std::vector<int> a = {0,1,2,3,4,5};
func(a.begin(), a.end());
return 0;
}
The error is:
error: ‘struct std::enable_if<std::is_same<typename
std::iterator_traits<_Iter>::iterator_category,std::random_access_iterator_tag::value,
typename std::iterator_traits<_Iterator>::difference_type>’ is not a
valid type for a template non-type parameter template<typename It,
std::enable_if<std::is_same<typename std::iterator_traits
error: no matching function for call to
‘func(std::vector<int>::iterator, std::vector<int>::iterator)’
func(a.begin(), a.end());
I can't parse your enable_if.
That works:
template<typename It, typename std::enable_if<std::is_same<typename std::iterator_traits<It>::iterator_category, std::random_access_iterator_tag>::value, int>::type = 0>
void func(const It& begin, const It& end)
{
std::cout << begin[0] << std::endl;
}
int main()
{
std::vector<int> a = {0,1,2,3,4,5};
func(a.begin(), a.end());
return 0;
}
But it can be that I misunderstood your intention.
You need typename = to use an anonymous type for SFINAE like that:
template<typename It,
typename = std::enable_if<std::is_same<typename std::iterator_traits<It>::iterator_category,
std::random_access_iterator_tag>::value,
typename std::iterator_traits<It>::difference_type>>
void func(const It& begin, const It& end)
{
std::cout << begin[0] << std::endl;
}
Or, you could use std::enable_if<...>::type as a return type for your function.
If you're working in C++17 or later, consider looking into the Concepts proposal that's making its way into C++20.

Selecting a functions implementation based on iterator::value_type

I'm looking for a reasonable way to select a sort algorithm based on the value type of the container.
In its current form I can deduce the proper sort(a, b) for integer/non-integer data.
#include <cstdlib>
#include <type_traits>
#include <algorithm>
#include <vector>
#include <iostream>
namespace sort_selector{
template<typename T>
void _radix_sort(T begin, T end){
// radix implementation
}
template<typename T>
typename std::enable_if<
std::is_integral<typename T::value_type>::value>::type
sort(T begin, T end){
std::cout << "Doing radix" << std::endl;
sort_selector::_radix_sort(begin, end);
}
template<typename T>
typename std::enable_if<
!std::is_integral<typename T::value_type>::value>::type
sort(T begin, T end){
std::cout << "Doing sort" << std::endl;
std::sort(begin, end);
}
}
int main(int argc, char** argv) {
std::vector<double> for_stdsort = {1, 4, 6, 2};
std::vector<int32_t> for_radixsort = {1, 4, 6, 2};
//std::array<int32_t, 4> array_for_radixsort = {1, 4, 6, 2};
sort_selector::sort(std::begin(for_stdsort), std::end(for_stdsort));
sort_selector::sort(std::begin(for_radixsort), std::end(for_radixsort));
//sort_selector::sort(std::begin(array_for_radixsort),
// std::end(array_for_radixsort));
return 0;
}
I would like to be able to use array-like iterators. (they have no ::value_type).
I would like to be able to distinguish between say, int32_t and int64_t.
I'm at a complete loss as how to achieve this in any reasonably simple way. I.e. not specializing for every instance.
Use std::iterator_traits<T>::value_type to retrieve the value type of an iterator; it works for pointers as well as class-type iterators.
For dispatching, I would use template specialization to select the proper implementation (Live Demo):
namespace sort_selector {
// Default to using std::sort
template <typename T, typename = void>
struct dispatcher {
template <typename Iterator>
static void sort(Iterator begin, Iterator end) {
std::cout << "Doing std::sort\n";
std::sort(begin, end);
}
};
// Use custom radix sort implementation for integral types
template <typename T>
struct dispatcher<T, typename std::enable_if<std::is_integral<T>::value>::type> {
template <typename Iterator>
static void sort(Iterator, Iterator) {
std::cout << "Doing radix\n";
// radix implementation
}
};
// Use some other specific stuff for int32_t
template <>
struct dispatcher<int32_t, void> {
template <typename Iterator>
static void sort(Iterator, Iterator) {
std::cout << "Specific overload for int32_t\n";
// Do something
}
};
// Dispatch appropriately
template <typename Iterator>
inline void sort(Iterator begin, Iterator end) {
dispatcher<typename std::iterator_traits<Iterator>::value_type>::sort(begin, end);
}
} // namespace sort_selector
You should probably constrain sort_selector::sort to require random access iterators so your error messages are more digestible when someone inevitably tries to pass an improper iterator type:
namespace sort_selector {
// Dispatch appropriately
template <typename Iterator>
inline void sort(Iterator begin, Iterator end) {
using traits = std::iterator_traits<Iterator>;
static_assert(
std::is_base_of<
std::random_access_iterator_tag,
typename traits::iterator_category
>::value, "sorting requires random access iterators");
dispatcher<typename traits::value_type>::sort(begin, end);
}
} // namespace sort_selector

Function template accepting nothing less than a bidirectional iterator or a pointer

I need a function template that accepts two iterators that could be pointers. If the two arguments are random_access iterators I want the return type to be an object of
std::iterator<random_access_iterator_tag, ...> type
else a
std::iterator<bidirectional_iterator_tag, ...> type.
I also want the code to refuse
compilation if the arguments are neither a bidirectional iterator, nor a pointer. I cannot have dependency on third party libraries e.g. Boost
Could you help me with the signature of this function so that it accepts bidirectional iterators as well as pointers, but not say input_iterator, output_iterator, forward_iterators.
One partial solution I can think of is the following
template<class T>
T foo( T iter1, T iter2) {
const T tmp1 = reverse_iterator<T>(iter1);
const T tmp2 = reverse_iterator<T>(iter2);
// do something
}
The idea is that if it is not bidirectional the compiler will not let me construct a reverse_iterator from it.
Here's an example with enable_if based on iterator tags. The substitution fails if the given T doesn't have a iterator_category typedef and so that overload isn't considered during overload resolution.
Since you can't use C++11, see the reference pages for enable_if and is_same to see how you can implement it by yourself.
#include <iterator>
#include <type_traits>
#include <iostream>
#include <vector>
#include <list>
template<typename T>
typename
std::enable_if<
std::is_same<
typename T::iterator_category,
std::bidirectional_iterator_tag
>::value,
T
>::type
foo(T it)
{
std::cout << "bidirectional\n";
return it;
}
template<typename T>
typename
std::enable_if<
std::is_same<
typename T::iterator_category,
std::random_access_iterator_tag
>::value,
T
>::type
foo(T it)
{
std::cout << "random access\n";
return it;
}
// specialization for pointers
template<typename T>
T* foo(T* it)
{
std::cout << "pointer\n";
return it;
}
int main()
{
std::list<int>::iterator it1;
std::vector<int>::iterator it2;
int* it3;
std::istream_iterator<int> it4;
foo(it1);
foo(it2);
foo(it3);
//foo(it4); // this one doesn't compile, it4 is an input iterator
}
Live example.
As per #JonathanWakely's comment, we can get rid of specialization for pointers if we use std::iterator_traits. The typename T::iterator_category part then becomes
typename std::iterator_traits<T>::iterator_category
a bit simpler than previous answer, no dependency on std::enable_if:
namespace detail
{
template<class T>
T do_foo(T iter1, T iter2, std::random_access_iterator_tag t)
{
cout << "do_foo random_access" << endl;
return iter1;
}
template<class T>
T do_foo(T iter1, T iter2, std::bidirectional_iterator_tag t)
{
cout << "do_foo bidirectional" << endl;
return iter1;
}
}
template<class T>
void foo(T iter1, T iter2)
{
typename std::iterator_traits<T>::iterator_category t;
detail::do_foo(iter1, iter2, t);
}
int main (int argc, const char * argv[])
{
std::vector<int> v;
foo(v.begin(), v.end());
std::list<int> l;
foo(l.begin(), l.end());
return 0;
}
The solution also supports other iterator_categories derived from std::random_access_iterator_tag or std::bidirectional_iterator_tag (should there be any), while std::same<> checks for strict category equality.