I have the code as below:
template <typename T, typename sepT = char>
void print2d(const T &data, sepT sep = ',') {
for(auto i = std::begin(data); i < std::end(data); ++i) {
decltype(*i) tmp = *i;
for(auto j = std::begin(tmp); j < std::end(tmp); ++j) {
std::cout << *j << sep;
}
std::cout << std::endl;
}
}
int main(){
std::vector<std::vector<int> > v = {{11}, {2,3}, {33,44,55}};
print2d(v);
int arr[2][2] = {{1,2},{3,4}};
print2d(arr);
return 0;
}
If I change the decltype
to auto
, it won't compile and complain (partial error):
2d_iterator.cpp: In instantiation of ‘void print2d(const T&, sepT) [with T = int [2][2]; sepT = char]’:
2d_iterator.cpp:21:21: required from here
2d_iterator.cpp:9:36: error: no matching function for call to ‘begin(const int*&)’
2d_iterator.cpp:9:36: note: candidates are:
In file included from /usr/lib/gcc/x86_64-redhat-linux/4.7.2/../../../../include/c++/4.7.2/string:53:0,
from /usr/lib/gcc/x86_64-redhat-linux/4.7.2/../../../../include/c++/4.7.2/bits/locale_classes.h:42,
from /usr/lib/gcc/x86_64-redhat-linux/4.7.2/../../../../include/c++/4.7.2/bits/ios_base.h:43,
from /usr/lib/gcc/x86_64-redhat-linux/4.7.2/../../../../include/c++/4.7.2/ios:43,
from /usr/lib/gcc/x86_64-redhat-linux/4.7.2/../../../../include/c++/4.7.2/ostream:40,
from /usr/lib/gcc/x86_64-redhat-linux/4.7.2/../../../../include/c++/4.7.2/iterator:64,
Why is this happening?
'auto' lets you declare a variable with a particular type whereas decltype lets you extract the type from the variable so decltype is sort of an operator that evaluates the type of passed expression.
Use auto and decltype to declare a template function whose return type depends on the types of its template arguments. Or, use auto and decltype to declare a template function that wraps a call to another function, and then returns the return type of the wrapped function.
In the C++ programming language, decltype is a keyword used to query the type of an expression. Introduced in C++11, its primary intended use is in generic programming, where it is often difficult, or even impossible, to express types that depend on template parameters.
The auto keyword is a simple way to declare a variable that has a complicated type. For example, you can use auto to declare a variable where the initialization expression involves templates, pointers to functions, or pointers to members.
The answer summed-up in one comment:
decltype
yieldsint(&)[2]
, whilst plainauto
forces a pointer conversion (same rules as template argument deduction). Just useauto&
. - Xeo
@Xeo's comment-answer basically says that because auto
involves the same rules as template argument type deduction, auto
deduces a pointer (int*
) type out of the source's array type (of i
, specifically int(&)[2]
).
There is something great in your code: it actually demonstrates how template type deduction behaves when the parameter is a reference and how the reference affects how the type is being deduced.
template <typename T, typename sepT = char>
void print2d(const T &data, sepT sep = ',') {
...
}
...
int arr[2][2] = {{1,2},{3,4}};
print2d(arr);
You can see that data
is of type const T&
, a reference to a const T
. Now, it is being passed with arr
, whose type is int[2][2]
, which is an array of two arrays of two int
s (whoo!). Now come template argument type deduction. On this situation, it rules that with data
being a reference, T
should be deduced with the original type of the argument, which is int[2][2]
. Then, it applies any qualifications to the parameter type to the parameter, and with data
's qualified type being const T&
, the const
and &
qualifiers are applied and so data
's type is const int (&) [2][2]
.
template <typename T, typename sepT = char>
void print2d(const T &data, sepT sep = ',') {
static_assert(std::is_same<T, int[2][2]>::value, "Fail");
static_assert(std::is_same<decltype(data), const int(&)[2][2]>::value, "Fail");
}
...
int arr[2][2] = {{1,2},{3,4}};
print2d(arr);
LIVE CODE
However, if data
would have been a non-reference, template argument type deduction rules that if the argument's type is an array type (e.g. int[2][2]
), the array type shall "decay" to its corresponding pointer type, thus making int[2][2]
into int(*)[2]
(plus const
if parameter is const
) (fix courtesy of @Xeo).
Great! I just explained the part that is entirely not what caused the error. (And I just explained a great deal of template magic)...
... Nevermind about that. Now to the error. But before we go, keep this on your mind:
auto == template argument type deduction
+ std::initializer_list deduction for brace init-lists // <-- This std::initializer_list thingy is not relevant to your problem,
// and is only included to prevent any outbreak of pedantry.
Now, your code:
for(auto i = std::begin(data); i < std::end(data); ++i) {
decltype(*i) tmp = *i;
for(auto j = std::begin(tmp); j < std::end(tmp); ++j) {
std::cout << *j << sep;
}
std::cout << std::endl;
}
Some prerequisites before the battle:
decltype(data) == const int (&) [2][2]
decltype(i) == const int (*) [2]
(see std::begin
), which is a pointer to an int[2]
.Now when you do decltype(*i) tmp = *i;
, decltype(*i)
would return const int(&)[2]
, a reference to an int[2]
(remember the word dereference). Thus, it is also tmp
's type. You preserved the original type by using decltype(*i)
.
However, when you do
auto tmp = *i;
Guess what decltype(tmp)
is: int*
! Why? Because all of the blabbery-blablablah above, and some template magic.
So, why the error with int*
? Because std::begin
expects an array type, not its lesser decayed-to pointer. Thus, auto j = std::begin(tmp)
would cause an error when tmp
is int*
.
How to solve (also tl;dr)?
Keep as-is. Use decltype
.
Guess what. Make your auto
ed variable a reference!
auto& tmp = *i;
LIVE CODE
or
const auto& tmp = *i;
if you don't intend to modify the contents of tmp
. (Greatness by Jon Purdy)
Moral of the story: A great comment saves a man a thousand words.
UPDATE: added const
to the types given by decltype(i)
and decltype(*i)
, as std::begin(data)
would return a const
pointer due to data
also being const
(fix by litb, thanks)
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