Implementing SFINAE

Pages: 12
I am trying to implement std::list and I am at the beginning and stuck on a constructor that take two iterators.

In that constructor is where I shall implement SFINAE, a topic very new to me which I understand what it is for but I don't know how to implement it correctly on my own as I've found many examples on several posts and I just don't know which one is the right one as I tried them in my code and tweak with them quite much but nothing worked and I haven't yet found a clear guide on SFINAE implementation.

Edit: I changed the code in the constructor that was wrong

Here is the code:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
#include <iostream>


using namespace std;

template <typename T>
class List
{
    struct Node
    {
        T data;
        Node* prev = nullptr;
        Node*next = nullptr;

        Node() = default;
        Node(T d) : data(d), prev(nullptr), next(nullptr) {}
    };

    Node* head, *tail;
    std::size_t l_size;

    //SFINAE -> This worked for the implementation of std::vector but doesn't seem to work here
    template<typename InputIterator>
    using required_input_iterator = std::enable_if<std::is_base_of_v<std::input_iterator_tag,
          typename std::iterator_traits<InputIterator>::iterator_category >>;

public:
    using reference = T&;
    using const_reference = const T&;
    using size_type = std::size_t;

    class const_iterator
    {
        Node* node = nullptr;

        friend class List;

        const_iterator (Node* p) : node(p) {}

    public:
        const_iterator ()  = default;
        const_iterator(const const_iterator& other) : node(other.node) {}

        reference operator*()
        {
            return node->data;
        }

        T* operator->()
        {
            return node->data;
        }

        // prefix operators
        const_iterator& operator++()
        {
            node = node->next;
            return *this;
        }

        const_iterator& operator--()
        {
            node = node->prev;
            return *this;
        }

        // postfix operators
        const_iterator operator++(int)
        {
            Node* temp = *this;
            node = node->next;
            return temp;
        }

        const_iterator operator--(int)
        {
            Node* temp = *this;
            node = node->prev;
            return temp;
        }

        friend bool operator== (const const_iterator& lhs, const const_iterator& rhs) noexcept
        {
            return lhs.node == rhs.node;
        }

        friend bool operator!= (const const_iterator& lhs, const const_iterator& rhs) noexcept
        {
            return lhs.node != rhs.node;
        }
    };

    class iterator
    {
        Node * node = nullptr;

        friend class List;

        iterator(Node* p) : node(p) {}

    public:
        iterator ()  = default;

        reference operator*()
        {
            return node->data;
        }

        reference operator->()
        {
            return node->data;
        }

        // prefix operators
        iterator& operator++()
        {
            node = node->next;
            return *this;
        }

        iterator& operator--()
        {
            node = node->prev;
            return *this;
        }

        // postfix operators
        iterator operator++(int)
        {
            Node* temp = *this;
            node = node->next;
            return temp;
        }

        iterator operator--(int)
        {
            Node* temp = *this;
            node = node->prev;
            return temp;
        }

        friend bool operator== (const iterator& lhs, const iterator& rhs) noexcept
        {
            return lhs.node == rhs.node;
        }

        friend bool operator!= (const iterator& lhs, const iterator& rhs) noexcept
        {
            return lhs.node != rhs.node;
        }

    };

    List();
    explicit List(size_type n);
    ~List() {};

    template<typename InputIterator, typename = required_input_iterator<InputIterator> >
    List(InputIterator first, InputIterator last)
    {


    }

    // iterators
    iterator begin() noexcept
    {
        return iterator(head->next);
    }

    iterator end() noexcept
    {
        return iterator(tail);
    }


};

template<typename T>
List<T>::List() : head(nullptr), tail(nullptr), l_size(0) { }

int main()
{

    List<int> lis;

    List<int> lis1(lis.begin(), lis.end());

}


The error it throws:
error: no matching function for call to 'List<int>::List(List<int>::iterator, List<int>::iterator)'|


Any help!?

Thanks!
Last edited on
This will work:

1. define trait like so:
1
2
template<typename InputIterator>
using required_input_iterator = std::enable_if_t<std::is_base_of_v<std::input_iterator_tag, InputIterator>>;


2. Inherit from input iterator
class iterator : public input_iterator_tag

3. declare constructor that takes iterators
1
2
template<typename InputIterator, typename = required_input_iterator<InputIterator> >
List(InputIterator first, InputIterator last);


4. define constructor
1
2
3
4
5
6
template<typename T>
template<typename InputIterator, typename SFINAE>
List<T>::List(InputIterator first, InputIterator last)
{
      // TODO: implementation missing
}

It works, thanks!

Could you explain how this works. Also I removed the : public std::input_iterator_tag and still "works".

template<typename InputIterator, typename SFINAE> -> Is SFINAE some keyword, what does it do?
OP,

typename = required_input_iterator<InputIterator>


This is completely unnecessary. You only have one constructor that takes two iterators. It will fail to compile either way if the required iterator is not an InputIterator. You don't need a type trait here.
Also I removed the : public std::input_iterator_tag and still "works"


It will "work" as long as you don't call the constructor that takes the input iterators. When you call it with your List iterator (that doesn't derive from std::input_iterator_tag) it will not compile, but it will work on, say, a std::vector::iterator.

Is SFINAE some keyword, what does it do?


No. As I mentioned in your previous post, SFINAE means substitution failure is not an error. If the substitution of a type in a template in the immediate context (type or template arguments) produces a failure, the next best template is chosen. If there is none, then a compiler error is given. That's it.

In your case, you only have one constructor that takes two input iterators. This will cause a compiler error if the iterator substituted does not support input operations, SFINAE or not. Since you have no other overloads that accept iterators, this is unnecessary.

A general recommendation is to not make things more complicated just because you can, especially when they do absolutely nothing. There is no need for this here. Just name the iterator InputIterator and the compiler error received will be self-explanatory.
Last edited on
Furthermore, your code as it is right now will cause a segmentation fault because you are calling begin() on an empty List, which you have defined to treat the begin() iterator as if it always contains an element. Therefore, even when the head pointer has been initialized to nullptr, you attempt to dereference it and access its next member. In order to fix this, you need to treat the case when begin() is called on an empty list and make it return the same as end(), in which case you can then adjust your code accordingly.
Last edited on
template<typename InputIterator, typename SFINAE> -> Is SFINAE some keyword, what does it do?


See declaration of constructor, from point 3 in my post, it takes TWO template parameters one of which is SFINAE "enablement"

Therefore constructor definition must also have 2 template parameters, regardless if you need and use only one, this "SFINAE" typename could have been avoided by just writing following:

template<typename InputIterator, typename> /* second type name not needed */

but it's not bad practice to be explicit, and it this case it's just a dummy parameter name to denote that second typename is needed because of SFINAE parameter in declaration.

There are cases where you can't avoid typing dummy template parameter name, and this is when your entry class is "SFINAE" and you want to define function outside that class, for example:

1
2
3
4
5
6
7
template <typename BASE_TYPE,
	typename = std::enable_if_t<std::is_base_of_v<BASE_TYPE, DERIVED_TYPE>>>
class MyObject
{
public:
	MyObject(); // constructor declaration
};


Here if you omit dummy typename called "SFINAE" in this case it won't compile:

1
2
3
4
5
// Constructor definition outside class, dummy typename is must!
template<typename BASE_TYPE, typename SFINAE>
MyObject<BASE_TYPE, SFINAE>::MyObject()
{
}


Of course this is not needed if definition is inside class.

EDIT:
if you want to compile this sample, just replace DERIVED_TYPE with some actual class name such as std::vector<int>
And then try to omit SFINAE type name and see what happens.
Last edited on
@TheToaster Thanks for the explanation.

There is no need for this here.
-> By 'this' you mean the SFINAE?

Just name the iterator InputIterator...
-> What do you mean?

But I have to say, SFINAE is seeming very tricky to implement and non-intuitive (at least for me).
I guess I am better off for now if I just write this costructor like this:
List(iterator first, iterator last)
and maybe wait for the C++20 concepts as it is said to make this better.
By 'this' you mean the SFINAE?

I mean there is no need to make a type trait to check if it is an input iterator. It is going to fail to compile if it is not an input iterator anyways. So yes, SFINAE is not needed to differentiate between template constructors since only one constructor takes two iterators.

What do you mean?


I mean if you do this:

List(InputIterator first, InputIterator last)

Then if client code causes a compilation failure because they passed a forward iterator instead of an input iterator, the compiler error will display the definition of the List constructor and you will be able to see that the template type parameter is named "InputIterator" and thus realize that the function is expecting an input iterator.

and maybe wait for the C++20 concepts as it is said to make this better.


I mean there's nothing stopping you from learning concepts now. C++20 is out and GCC 10 supports concepts afaik.
Last edited on
@malibor, of I understand it now, seemed odd at first but now it makes sense.

I made some tests with the SFINAE implementation you gave me and it fails.

When I instantiate the List like this:

List<int> lis(5, 6);

it calls the constructor that I am trying to implement the SFINAE instead of this ctor:

List(size_type n, const_reference v);
List<int> lis(5, 6);
I don't understand what are you trying to do?

passing integers to constructor that expects input iterators?

If you want to pass integers define additional constructor that takes numbers.
List(size_type n, const_reference v);


Ah, now THIS is an example of where you would want to use SFINAE. You didn't put this constructor in your original post, so I didn't know you had another constructor that could conflict.

In this case, you can use some kind of basic SFINAE condition that disables the first constructor if it is an iterator. Something like this could work:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    List(size_type n, const_reference v = T{}) {std::cout << "function 2" << std::endl;}

    template<typename InputIterator, typename = decltype(*std::declval<InputIterator>())>
    List(InputIterator first, InputIterator last)
    {
        std::cout << "function 1" << std::endl;

    }
//...

#include <vector>
int main()
{

    vector<int> lis{1,2,3,4,5};

    List<int> lis1(lis.begin(), lis.end());
    List<int> lis2(2,3);

}


This basically checks if the InputIterator type can be dereferenced using operator*. If it can't, then it is a substitution failure and the other constructor is selected. Note, this condition will apply for ALL iterators, not just input ones. However, even if another Iterator type is provided, it will still fail to compile if you use an iterator that doesn't support input operations when the body of the constructor is substituted (which is after the immediate context of the template arguments and the return type).

There's probably a better way to write this check, but oh well :) It works on raw pointer types as iterators as well as iterator classes, so this is probably a good solution if you want to construct a list from, say, a raw C-style array.

@malibor:
I don't understand what are you trying to do? passing integers to constructor that expects input iterators?


He explained that he was trying to implement the following constructor:
List(size_type n, const_reference v);

But the iterator constructor was always chosen instead of this one. He didn't have it in his original post, so I assumed he didn't have any other constructors that could conflict with the template one.
Last edited on
Sorry I left a lot of methods out of the post to make it easy to focus on the question but I really thought I had put that constructor in this code though.
I mistakenly assumed that by leaving things out I was making the question better when I wasn't. Sorry!!


I will edit the post and provide all the class' methods.
The post will exceed its length if I add the whole class so I will add here, hope it is ok:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
template <typename T>
class List
{
    struct Node
    {
        T data;
        Node* prev = nullptr;
        Node*next = nullptr;

        Node() = default;
        Node(T d) : data(d), prev(nullptr), next(nullptr) {}
    };

    Node* head = nullptr;
    Node *tail = nullptr;
    std::size_t l_size = 0;

    //SFINAE


public:
    using reference = T&;
    using const_reference = const T&;
    using size_type = std::size_t;

    class const_iterator
    {
        Node* node = nullptr;

        friend class List;

        const_iterator (Node* p) : node(p) {}

    public:
        const_iterator ()  = default;
        const_iterator(const const_iterator& other) : node(other.node) {}

        reference operator*()
        {
            return node->data;
        }

        T* operator->()
        {
            return node->data;
        }

        // prefix operators
        const_iterator& operator++()
        {
            node = node->next;
            return *this;
        }

        const_iterator& operator--()
        {
            node = node->prev;
            return *this;
        }

        // postfix operators
        const_iterator operator++(int)
        {
            Node* temp = *this;
            node = node->next;
            return temp;
        }

        const_iterator operator--(int)
        {
            Node* temp = *this;
            node = node->prev;
            return temp;
        }

        friend bool operator== (const const_iterator& lhs, const const_iterator& rhs) noexcept
        {
            return lhs.node == rhs.node;
        }

        friend bool operator!= (const const_iterator& lhs, const const_iterator& rhs) noexcept
        {
            return lhs.node != rhs.node;
        }
    };

    class iterator : public std::input_iterator_tag
    {
        Node * node = nullptr;

        friend class List;

        iterator(Node* p) : node(p) {}

    public:
        iterator ()  = default;

        reference operator*()
        {
            return node->data;
        }

        reference operator->()
        {
            return node->data;
        }

        // prefix operators
        iterator& operator++()
        {
            node = node->next;
            return *this;
        }

        iterator& operator--()
        {
            node = node->prev;
            return *this;
        }

        // postfix operators
        iterator operator++(int)
        {
            Node* temp = *this;
            node = node->next;
            return temp;
        }

        iterator operator--(int)
        {
            Node* temp = *this;
            node = node->prev;
            return temp;
        }

        friend bool operator== (const iterator& lhs, const iterator& rhs) noexcept
        {
            return lhs.node == rhs.node;
        }

        friend bool operator!= (const iterator& lhs, const iterator& rhs) noexcept
        {
            return lhs.node != rhs.node;
        }

    };

    template<typename InputIterator>
    using required_input_iterator = std::enable_if<std::is_same_v<List<T>::iterator, InputIterator >>;

    using reverse_iterator = std::reverse_iterator<iterator>;
    using const_reverse_iterator = std::reverse_iterator<const_iterator>;

    List() = default;
    explicit List(size_type n);
    List(size_type n, const_reference v);
    List(const std::initializer_list<T>& i_list);
    template<typename InputIterator, typename = required_input_iterator<InputIterator>>
    List(InputIteratorfirst, InputIteratorlast);
    List(const List& src);
    List& operator=(const List& src);
    List(const List&& src);
    //~List();


    void assign(size_type n, const_reference v);
    void assign(const std::initializer_list<T>& i_list);
    template<typename InputIterator> void assign(InputIterator first, InputIterator last);

    // element acces
    reference front();
    const_reference front() const;
    reference back();
    const_reference back() const;

    // iterators
    iterator begin() noexcept
    {
        return iterator(head);
    }
    const_iterator begin() const noexcept
    {
        return iterator(head);
    }
    iterator end() noexcept
    {
        return iterator(tail);
    }
    const_iterator end() const noexcept
    {
        return iterator(tail);
    }
    const_iterator cbegin() const noexcept;
    const_iterator  cend() const;
    reverse_iterator rbegin() noexcept;
    const_reverse_iterator crbegin() const noexcept;
    reverse_iterator rend() noexcept;
    const_reverse_iterator crend() const noexcept;

    // capacity
    size_type size() const noexcept;
    bool empty() const noexcept;

    // modifiers
    void clear() noexcept;
    iterator insert(const_iterator pos, const_reference v);
    iterator insert(const_iterator pos, const T&& v);
    iterator insert(const_iterator pos, size_type n, const_reference v);
    template<typename InputIterator> iterator insert(InputIterator first, InputIterator last);
    iterator insert(const_iterator pos, std::initializer_list<T> i_list);
    template<typename... Args> iterator emplace(const_iterator pos, Args&&... args );
    iterator erase(const_iterator pos);
    iterator erase(const_iterator first, const_iterator last);
    void push_back(const_reference v);
    void push_back(T&& v);
    template<typename... Args> reference emplace_back(Args&&...args);
    void pop_back();
    void push_front(const_reference v);
    void push_front(T&& v);
    template<typename... Args> reference emplace_front(Args&&...args);
    void pop_front();
    void resize(size_type new_size);
    void resize(size_type new_size, const_reference v);
    void swap(List<T> other) noexcept;
};
Did you try the code I gave you with the SFINAE test?
1
2
3
4
5
6
7
8
    List(size_type n, const_reference v = T{}) {std::cout << "function 2" << std::endl;}

    template<typename InputIterator, typename = decltype(*std::declval<InputIterator>())>
    List(InputIterator first, InputIterator last)
    {
        std::cout << "function 1" << std::endl;

    }
Last edited on
@TheToaster
I mean there's nothing stopping you from learning concepts now. C++20 is out and GCC 10 supports concepts afaik.

Yeah you are right. When I said 'wait' I was referring to when I have C++20 working compiler ready to use though.
However, I have been trying to build GCC 10 since yesterday. Haven't succeeded yet, some errors when running make. I will focus on it in the next few days and learn more about concepts as well.

I will try the code you gave me. Thanks.
Last edited on
The right way to do this is to query std::iterator_traits<T>::iterator_category as discussed in OP's prior thread
http://www.cplusplus.com/forum/beginner/270488/#msg1165620

That type alias has been misquoted several times in this thread, with different issues each time.
Last edited on
The right way to do this is to query std::iterator_traits<T>::iterator_category


That would work. But of course, that would prevent the following from working I believe:

1
2
int arr[] = {1,2,3,4,5};
List<int> list(arr, arr+5);


You could do it like this:

1
2
template<typename InputIterator, typename = decltype(*std::declval<InputIterator>())>
    List(InputIterator first, InputIterator last);


And this would work for all iterators and pointers since they support operator*(). And if the user accidentally passes an iterator that is NOT an input iterator, it would fail to compile anyways in the non-immediate context of the function body. AFAIK, there are no other constructors that would conflict in OP's implementation that would require such a strict type trait.
Last edited on
But of course, that would prevent the following from working I believe

It works, as discussed in the prior thread
http://www.cplusplus.com/forum/beginner/270488/#msg1165699

It is poor practice to implement weak type constraints. Overly-weak type constraints restrict implementation flexibility and invite avoidable errors. Lengthy discussion here:
http://www.stroustrup.com/good_concepts.pdf
Last edited on
It works, as discussed in the prior thread

Well, it doesn't seem to be working here:
https://onlinegdb.com/HJAXlDki8

And a raw pointer is not a class type, so you wouldn't be able to query it with std::iterator_traits, right?

Never mind. I guess you can. But it doesn't seem to work in the above code
Last edited on
You didn't use the right definition of required_input_iterator.

[Edit. Sorry, I shouldn't snap at you.]
Last edited on
Pages: 12