Deduction guides, initializer_list, and the type deduction process












2














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?










share|improve this question





























    2














    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?










    share|improve this question



























      2












      2








      2







      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?










      share|improve this question















      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






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Nov 13 at 14:56









      Barry

      177k18304558




      177k18304558










      asked Nov 12 at 19:36









      Igor R.

      10.6k12959




      10.6k12959
























          2 Answers
          2






          active

          oldest

          votes


















          2














          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.






          share|improve this answer























          • 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



















          1














          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






          share|improve this answer





















            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
            });


            }
            });














            draft saved

            draft discarded


















            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









            2














            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.






            share|improve this answer























            • 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
















            2














            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.






            share|improve this answer























            • 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














            2












            2








            2






            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.






            share|improve this answer














            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.







            share|improve this answer














            share|improve this answer



            share|improve this answer








            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


















            • 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













            1














            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






            share|improve this answer


























              1














              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






              share|improve this answer
























                1












                1








                1






                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






                share|improve this answer












                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







                share|improve this answer












                share|improve this answer



                share|improve this answer










                answered Nov 12 at 20:34









                Shafik Yaghmour

                125k23322532




                125k23322532






























                    draft saved

                    draft discarded




















































                    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.




                    draft saved


                    draft discarded














                    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





















































                    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







                    Popular posts from this blog

                    Bressuire

                    Vorschmack

                    Quarantine