Deduction guides, initializer_list, and the type deduction process
Consider the following code:
#include <initializer_list>
#include <utility>
template<class T>
struct test
{
test(const std::pair<T, T> &)
{}
};
template<class T>
test(std::initializer_list<T>) -> test<T>;
int main()
{
test t{{1, 2}};
}
I would like to understand why this trick with initializer_list
compiles. It looks like at first, {1, 2} is treated as an initializer_list
, but then it's re-interpreted as a list-initialization of pair
.
What exactly happens here, step-by-step?
c++ language-lawyer c++17 initializer-list list-initialization
add a comment |
Consider the following code:
#include <initializer_list>
#include <utility>
template<class T>
struct test
{
test(const std::pair<T, T> &)
{}
};
template<class T>
test(std::initializer_list<T>) -> test<T>;
int main()
{
test t{{1, 2}};
}
I would like to understand why this trick with initializer_list
compiles. It looks like at first, {1, 2} is treated as an initializer_list
, but then it's re-interpreted as a list-initialization of pair
.
What exactly happens here, step-by-step?
c++ language-lawyer c++17 initializer-list list-initialization
add a comment |
Consider the following code:
#include <initializer_list>
#include <utility>
template<class T>
struct test
{
test(const std::pair<T, T> &)
{}
};
template<class T>
test(std::initializer_list<T>) -> test<T>;
int main()
{
test t{{1, 2}};
}
I would like to understand why this trick with initializer_list
compiles. It looks like at first, {1, 2} is treated as an initializer_list
, but then it's re-interpreted as a list-initialization of pair
.
What exactly happens here, step-by-step?
c++ language-lawyer c++17 initializer-list list-initialization
Consider the following code:
#include <initializer_list>
#include <utility>
template<class T>
struct test
{
test(const std::pair<T, T> &)
{}
};
template<class T>
test(std::initializer_list<T>) -> test<T>;
int main()
{
test t{{1, 2}};
}
I would like to understand why this trick with initializer_list
compiles. It looks like at first, {1, 2} is treated as an initializer_list
, but then it's re-interpreted as a list-initialization of pair
.
What exactly happens here, step-by-step?
c++ language-lawyer c++17 initializer-list list-initialization
c++ language-lawyer c++17 initializer-list list-initialization
edited Nov 13 at 14:56
Barry
177k18304558
177k18304558
asked Nov 12 at 19:36
Igor R.
10.6k12959
10.6k12959
add a comment |
add a comment |
2 Answers
2
active
oldest
votes
It compiles because that's how class template deduction guides work.
Deduction guides are hypothetical constructors of the type. They don't really exist. Their only purpose is to determine how to deduce class template parameters.
Once the deduction is made, the actual C++ code takes over with a specific instantion of test
. So instead of test t{{1, 2}};
, the compiler behaves as if you had said test<int> t{{1, 2}};
.
test<int>
has a constructor that takes a pair<int, int>
, which can match the values in the braced-init-list, so that's what gets called.
This kind of thing was done in part to allow aggregates to participate in class template argument deduction. Aggregates don't have user-provided constructors, so if deduction guides were limited to just real constructors, you couldn't have aggregates work.
So we get to have this class template deduction guide for std::array
:
template <class T, class... U>
array(T, U...) -> array<T, 1 + sizeof...(U)>;
This allows std::array arr = {2, 4, 6, 7};
to work. It deduces both the template argument and the length from the guide, but since the guide is not a constructor, array
gets to remain an aggregate.
I understand that the guide is a "hypothetical" constructor. What confused me was the fact that the {1, 2} construct was treated differently at the different stages of this process...
– Igor R.
Nov 12 at 21:23
add a comment |
With your deduction guide we end up with what is equivalent to:
test<int> t{{1, 2}};
This works due to list initialization, section dcl.init.listp3.7 which says:
Otherwise, if T is a class type, constructors are considered. The
applicable constructors are enumerated and the best one is chosen
through overload resolution ([over.match], [over.match.list]). If a
narrowing conversion (see below) is required to convert any of the
arguments, the program is ill-formed. [ Example:
struct S {
S(std::initializer_list<double>); // #1
S(std::initializer_list<int>); // #2
S(); // #3
// ...
};
S s1 = { 1.0, 2.0, 3.0 }; // invoke #1
S s2 = { 1, 2, 3 }; // invoke #2
S s3 = { }; // invoke #3
— end example ] [ Example:
struct Map {
Map(std::initializer_list<std::pair<std::string,int>>);
};
Map ship = {{"Sophie",14}, {"Surprise",28}};
— end example ] [ Example:
struct S {
// no initializer-list constructors
S(int, double, double); // #1
S(); // #2
// ...
};
S s1 = { 1, 2, 3.0 }; // OK: invoke #1
S s2 { 1.0, 2, 3 }; // error: narrowing
S s3 { }; // OK: invoke #2
— end example ]
Otherwise we have a non-deduced context
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53268957%2fdeduction-guides-initializer-list-and-the-type-deduction-process%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
2 Answers
2
active
oldest
votes
2 Answers
2
active
oldest
votes
active
oldest
votes
active
oldest
votes
It compiles because that's how class template deduction guides work.
Deduction guides are hypothetical constructors of the type. They don't really exist. Their only purpose is to determine how to deduce class template parameters.
Once the deduction is made, the actual C++ code takes over with a specific instantion of test
. So instead of test t{{1, 2}};
, the compiler behaves as if you had said test<int> t{{1, 2}};
.
test<int>
has a constructor that takes a pair<int, int>
, which can match the values in the braced-init-list, so that's what gets called.
This kind of thing was done in part to allow aggregates to participate in class template argument deduction. Aggregates don't have user-provided constructors, so if deduction guides were limited to just real constructors, you couldn't have aggregates work.
So we get to have this class template deduction guide for std::array
:
template <class T, class... U>
array(T, U...) -> array<T, 1 + sizeof...(U)>;
This allows std::array arr = {2, 4, 6, 7};
to work. It deduces both the template argument and the length from the guide, but since the guide is not a constructor, array
gets to remain an aggregate.
I understand that the guide is a "hypothetical" constructor. What confused me was the fact that the {1, 2} construct was treated differently at the different stages of this process...
– Igor R.
Nov 12 at 21:23
add a comment |
It compiles because that's how class template deduction guides work.
Deduction guides are hypothetical constructors of the type. They don't really exist. Their only purpose is to determine how to deduce class template parameters.
Once the deduction is made, the actual C++ code takes over with a specific instantion of test
. So instead of test t{{1, 2}};
, the compiler behaves as if you had said test<int> t{{1, 2}};
.
test<int>
has a constructor that takes a pair<int, int>
, which can match the values in the braced-init-list, so that's what gets called.
This kind of thing was done in part to allow aggregates to participate in class template argument deduction. Aggregates don't have user-provided constructors, so if deduction guides were limited to just real constructors, you couldn't have aggregates work.
So we get to have this class template deduction guide for std::array
:
template <class T, class... U>
array(T, U...) -> array<T, 1 + sizeof...(U)>;
This allows std::array arr = {2, 4, 6, 7};
to work. It deduces both the template argument and the length from the guide, but since the guide is not a constructor, array
gets to remain an aggregate.
I understand that the guide is a "hypothetical" constructor. What confused me was the fact that the {1, 2} construct was treated differently at the different stages of this process...
– Igor R.
Nov 12 at 21:23
add a comment |
It compiles because that's how class template deduction guides work.
Deduction guides are hypothetical constructors of the type. They don't really exist. Their only purpose is to determine how to deduce class template parameters.
Once the deduction is made, the actual C++ code takes over with a specific instantion of test
. So instead of test t{{1, 2}};
, the compiler behaves as if you had said test<int> t{{1, 2}};
.
test<int>
has a constructor that takes a pair<int, int>
, which can match the values in the braced-init-list, so that's what gets called.
This kind of thing was done in part to allow aggregates to participate in class template argument deduction. Aggregates don't have user-provided constructors, so if deduction guides were limited to just real constructors, you couldn't have aggregates work.
So we get to have this class template deduction guide for std::array
:
template <class T, class... U>
array(T, U...) -> array<T, 1 + sizeof...(U)>;
This allows std::array arr = {2, 4, 6, 7};
to work. It deduces both the template argument and the length from the guide, but since the guide is not a constructor, array
gets to remain an aggregate.
It compiles because that's how class template deduction guides work.
Deduction guides are hypothetical constructors of the type. They don't really exist. Their only purpose is to determine how to deduce class template parameters.
Once the deduction is made, the actual C++ code takes over with a specific instantion of test
. So instead of test t{{1, 2}};
, the compiler behaves as if you had said test<int> t{{1, 2}};
.
test<int>
has a constructor that takes a pair<int, int>
, which can match the values in the braced-init-list, so that's what gets called.
This kind of thing was done in part to allow aggregates to participate in class template argument deduction. Aggregates don't have user-provided constructors, so if deduction guides were limited to just real constructors, you couldn't have aggregates work.
So we get to have this class template deduction guide for std::array
:
template <class T, class... U>
array(T, U...) -> array<T, 1 + sizeof...(U)>;
This allows std::array arr = {2, 4, 6, 7};
to work. It deduces both the template argument and the length from the guide, but since the guide is not a constructor, array
gets to remain an aggregate.
edited Nov 12 at 20:38
answered Nov 12 at 20:32
Nicol Bolas
282k33465641
282k33465641
I understand that the guide is a "hypothetical" constructor. What confused me was the fact that the {1, 2} construct was treated differently at the different stages of this process...
– Igor R.
Nov 12 at 21:23
add a comment |
I understand that the guide is a "hypothetical" constructor. What confused me was the fact that the {1, 2} construct was treated differently at the different stages of this process...
– Igor R.
Nov 12 at 21:23
I understand that the guide is a "hypothetical" constructor. What confused me was the fact that the {1, 2} construct was treated differently at the different stages of this process...
– Igor R.
Nov 12 at 21:23
I understand that the guide is a "hypothetical" constructor. What confused me was the fact that the {1, 2} construct was treated differently at the different stages of this process...
– Igor R.
Nov 12 at 21:23
add a comment |
With your deduction guide we end up with what is equivalent to:
test<int> t{{1, 2}};
This works due to list initialization, section dcl.init.listp3.7 which says:
Otherwise, if T is a class type, constructors are considered. The
applicable constructors are enumerated and the best one is chosen
through overload resolution ([over.match], [over.match.list]). If a
narrowing conversion (see below) is required to convert any of the
arguments, the program is ill-formed. [ Example:
struct S {
S(std::initializer_list<double>); // #1
S(std::initializer_list<int>); // #2
S(); // #3
// ...
};
S s1 = { 1.0, 2.0, 3.0 }; // invoke #1
S s2 = { 1, 2, 3 }; // invoke #2
S s3 = { }; // invoke #3
— end example ] [ Example:
struct Map {
Map(std::initializer_list<std::pair<std::string,int>>);
};
Map ship = {{"Sophie",14}, {"Surprise",28}};
— end example ] [ Example:
struct S {
// no initializer-list constructors
S(int, double, double); // #1
S(); // #2
// ...
};
S s1 = { 1, 2, 3.0 }; // OK: invoke #1
S s2 { 1.0, 2, 3 }; // error: narrowing
S s3 { }; // OK: invoke #2
— end example ]
Otherwise we have a non-deduced context
add a comment |
With your deduction guide we end up with what is equivalent to:
test<int> t{{1, 2}};
This works due to list initialization, section dcl.init.listp3.7 which says:
Otherwise, if T is a class type, constructors are considered. The
applicable constructors are enumerated and the best one is chosen
through overload resolution ([over.match], [over.match.list]). If a
narrowing conversion (see below) is required to convert any of the
arguments, the program is ill-formed. [ Example:
struct S {
S(std::initializer_list<double>); // #1
S(std::initializer_list<int>); // #2
S(); // #3
// ...
};
S s1 = { 1.0, 2.0, 3.0 }; // invoke #1
S s2 = { 1, 2, 3 }; // invoke #2
S s3 = { }; // invoke #3
— end example ] [ Example:
struct Map {
Map(std::initializer_list<std::pair<std::string,int>>);
};
Map ship = {{"Sophie",14}, {"Surprise",28}};
— end example ] [ Example:
struct S {
// no initializer-list constructors
S(int, double, double); // #1
S(); // #2
// ...
};
S s1 = { 1, 2, 3.0 }; // OK: invoke #1
S s2 { 1.0, 2, 3 }; // error: narrowing
S s3 { }; // OK: invoke #2
— end example ]
Otherwise we have a non-deduced context
add a comment |
With your deduction guide we end up with what is equivalent to:
test<int> t{{1, 2}};
This works due to list initialization, section dcl.init.listp3.7 which says:
Otherwise, if T is a class type, constructors are considered. The
applicable constructors are enumerated and the best one is chosen
through overload resolution ([over.match], [over.match.list]). If a
narrowing conversion (see below) is required to convert any of the
arguments, the program is ill-formed. [ Example:
struct S {
S(std::initializer_list<double>); // #1
S(std::initializer_list<int>); // #2
S(); // #3
// ...
};
S s1 = { 1.0, 2.0, 3.0 }; // invoke #1
S s2 = { 1, 2, 3 }; // invoke #2
S s3 = { }; // invoke #3
— end example ] [ Example:
struct Map {
Map(std::initializer_list<std::pair<std::string,int>>);
};
Map ship = {{"Sophie",14}, {"Surprise",28}};
— end example ] [ Example:
struct S {
// no initializer-list constructors
S(int, double, double); // #1
S(); // #2
// ...
};
S s1 = { 1, 2, 3.0 }; // OK: invoke #1
S s2 { 1.0, 2, 3 }; // error: narrowing
S s3 { }; // OK: invoke #2
— end example ]
Otherwise we have a non-deduced context
With your deduction guide we end up with what is equivalent to:
test<int> t{{1, 2}};
This works due to list initialization, section dcl.init.listp3.7 which says:
Otherwise, if T is a class type, constructors are considered. The
applicable constructors are enumerated and the best one is chosen
through overload resolution ([over.match], [over.match.list]). If a
narrowing conversion (see below) is required to convert any of the
arguments, the program is ill-formed. [ Example:
struct S {
S(std::initializer_list<double>); // #1
S(std::initializer_list<int>); // #2
S(); // #3
// ...
};
S s1 = { 1.0, 2.0, 3.0 }; // invoke #1
S s2 = { 1, 2, 3 }; // invoke #2
S s3 = { }; // invoke #3
— end example ] [ Example:
struct Map {
Map(std::initializer_list<std::pair<std::string,int>>);
};
Map ship = {{"Sophie",14}, {"Surprise",28}};
— end example ] [ Example:
struct S {
// no initializer-list constructors
S(int, double, double); // #1
S(); // #2
// ...
};
S s1 = { 1, 2, 3.0 }; // OK: invoke #1
S s2 { 1.0, 2, 3 }; // error: narrowing
S s3 { }; // OK: invoke #2
— end example ]
Otherwise we have a non-deduced context
answered Nov 12 at 20:34
Shafik Yaghmour
125k23322532
125k23322532
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53268957%2fdeduction-guides-initializer-list-and-the-type-deduction-process%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown