Many container class templates have, among others, a constructor that takes a count and a sample element as argument, and another constructor that takes a pair of input iterators. Other methods like insert
exhibit the same pattern. A naive implementation will run into trouble when the template is instantiated with an integral type, and the constructor (or other method) is called with a pair of integers: their types being equal, the input iterator method will give a valid argument type deduction, but then fail to compile.
I'm looking for an elegant way to specify that the input iterator method is to participate in overloading only for (equal) argument types that are actually valid input iterator types, or at the very least not to participate for integer types. I have got the impression that SFINAE is the way to go (but would be delighted to be told differently), but when reading about it, frankly I don't quite understand the rules, and moreover the solutions presented in examples hardly qualify as elegant.
As a side restriction, I would like my code to comile with gcc 4.6 which has incomplete C++11 support. Notably I would like to avoid using template aliases. Here's my clumsy attempt (bare bones):
#include <iterator>
#include <type_traits>
#include <vector>
template <typename I>
struct input_iter : public std::integral_constant<bool,
not std::is_integral<I>::value
and std::is_base_of
<std::input_iterator_tag
,typename std::iterator_traits<I>::iterator_category
>::value >
{};
template<typename T>
struct container
{
container (size_t count, const T& init);
template <typename InputIt,
typename = typename std::enable_if<input_iter<InputIt>::value>::type >
container (InputIt first,InputIt last);
};
typedef container<int> int_cont;
void f()
{ std::vector<int> v (5,3);
int_cont a1 (3u,6); // first constructor
int_cont a2 (3u,6); // first constructor
int_cont a3 (3,6); // first constructor
int_cont a4 (3,6); // first constructor
int_cont a5 (3,6); // first constructor
int_cont b(v.begin(),v.end()); // second constructor
}
And here's a live example. The class template input_iter
is trying to do things a bit redundantly: check that the type is not integral and then that it is actually an input iterator. Preferably I'd use just the second part, but that does not work; I get a template instantiation error when trying int
for I
(no iterator_category
), and apparently this is not SFINAE. While I don't understand just why this is so, I added the first part to avoid the error, using the laziness of the and
(&&
) operator, but apparently to no avail. I can actually get this to compile by removing the second part of the condition, so my real question is not so much to get this to work in some way or another, but to understand what is going on.
One curious thing I noticed is that g++
only gives an error message mentioning the definition of a3
. So on one hand, making one argument unsigned apparently avoids trying the iterator overload (even though the other argument can easily be converted to unsigned), and on the other hand repeating the offending definition of a3
for a4
and a5
does not repeat the error message (but if one repairs the a3
definition, surely gcc
balks at the a4
definition). Also note that clang++
does not point at any specific definition of one of the a
variables, though repairing all of them makes it silent.
What am I doing wrong, and/or should obviously be doing differently?
With help of the comments, and some more experimenting, I will try to formulate an answer myself.
I gather that Substitution Failure is only (SFI)NAE if it occurs in the "immediate context" of the relevant declaration, which is apparently a consecrated (but not very clearly explained) term used in 14.8.2;8 where it says "Only invalid types and expressions in the immediate context of the function type and its template parameter types can result in a deduction failure". Leaving aside that constructor methods don't really have any type, the failure/error in the example arises when trying to construct the defaulted type for the (otherwise unused) second template argument, but substituting a derived integral type for InputIt
into the fragment typename std::enable_if...::type
. Finding the value of the Boolean (nontype) template argument of std::enable_if
is in itself performed in the "immediate context" (of the type substitution); the previous paragraph 14.8.2;7 is clear about this ("expressions include not only constant expressions such as those that appear in array bounds or as nontype template arguments..."). However computing that Boolean value requires forming the class template specialisation input_iter<InputIt>
, and that is an auxiliary activity that is no longer performed in the immediate context. It turns out that during that template specialisation another template specialisation is performed, namely
std::iterator_traits<InputIt>
; this is not in the immediate context either, but this fact is irrelevant here since the latter specialisation never fails. However the resulting structure does not have an appropriately defined iterator_category
member when InputIt
is deduced to be an integral type, which causes the specialisation input_iter<InputIt>
to fail in that case. Not being in the immediate context of the original type substitution, this failure make the program ill-formed.
So the real culprit, the element that separates the failure from the context where SFINEA would apply, is the specialisation input_iter<InputIt>
, whereas std::iterator_traits<InputIt>
is only indirectly involved. As a consequence, the example can be made to compile by cutting out the type derivation performed by the input_iter
template, and feeding the boolean expression directly into enable_if
. Since I wanted to avoid using template aliases, this requires (since there is no such thing as boolean-value templates) forgetting input_iter
altogether and moving the whole expression it contained into the constructor template declaration, giving
template <typename InputIt,
typename = typename std::enable_if<not std::is_integral<InputIt>::value
and std::is_base_of
<std::input_iterator_tag
,typename std::iterator_traits<InputIt>::iterator_category
>::value>::type >
container (InputIt first,InputIt last);
The std::is_integral
part is not really useful, and can be omitted, leaving just the std::is_base_of
part. The resulting code compiles and runs correctly, in fact even with gcc 4.6. This shows that in spite of appearances, one can use std::iterator_traits
with SFINAE. When template aliases are available, one of those can be used to lift the enable_if<...>::type
part out of the constructor declaration to make it look more appetising, but without them I cannot see how to do it and still correctly invoke SFINAE.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With