How can I check type T is among parameter pack Ts…?How can I profile C++ code running on Linux?Is there a standard sign function (signum, sgn) in C/C++?Check if a string contains a string in C++Pretty-print C++ STL containersExpanding parameter pack containing initializer_list to constructorWhy std::fstream returns void instead of boolBool sorted insert function with check if int already exist in listType checking template class parametersCould not deduce template argument for std::function from std::bindDoes the C++ standard allow for an uninitialized bool to crash a program?

Why will we fail creating a self sustaining off world colony?

Identifying positions of the last TRUEs in a sequence of TRUEs and FALSEs

Five 5-cent coins touching each other

How to stop QGIS from looking for the wrong PostgreSQL host address in an existing workproject?

Odd PCB Layout for Voltage Regulator

Basic calculations in PGF/TikZ for loop

German idiomatic equivalents of 能骗就骗 (if you can cheat, then cheat)

iMac 2019: Can I mix the old modules with the new ones when upgrading RAM?

Drawing a sigmoid function and its derivative in tikz

Is it advisable to inform the CEO about his brother accessing his office?

How far can gerrymandering go?

What verb goes with "coup"?

What is this fluorinated organic substance?

Installed software from source, how to say yum not to install it from package?

Why would Dementors torture a Death Eater if they are loyal to Voldemort?

What was the point of separating stdout and stderr?

Active wildlife outside the window- Good or Bad for Cat psychology?

What are the children of two Muggle-borns called?

What does this Pokemon Trainer mean by saying the player is "SHELLOS"?

What's the lunar calendar of two moons

Are you required to spend hit dice to take a short rest?

Why should I allow multiple IP addresses on a website for a single session?

How soon after takeoff can you recline your airplane seat?

How to count the number of bytes in a file, grouping the same bytes?



How can I check type T is among parameter pack Ts…?


How can I profile C++ code running on Linux?Is there a standard sign function (signum, sgn) in C/C++?Check if a string contains a string in C++Pretty-print C++ STL containersExpanding parameter pack containing initializer_list to constructorWhy std::fstream returns void instead of boolBool sorted insert function with check if int already exist in listType checking template class parametersCould not deduce template argument for std::function from std::bindDoes the C++ standard allow for an uninitialized bool to crash a program?













32















I want to write a function to return true if T is one of Ts...



template<class T, class... Ts>
bool is_one_of<T, Ts...>();


For example, is_one_of<int, double, int, float> returns true, and is_one_of<int, double, std::string, bool, bool> returns false.



My own implementation is



template<class T1, class T2>
bool is_one_of<T1, T2>()
return std::is_same<T1, T2>;


template<class T1, class T2, class... Ts>
bool is_one_of<T1, T2, Ts...>()
if (std::is_same<T1, T2>)
return true;

else
return is_one_of<T1, Ts...>();




This check seems common to me so I wonder if there's already such a function in the standard library.










share|improve this question




























    32















    I want to write a function to return true if T is one of Ts...



    template<class T, class... Ts>
    bool is_one_of<T, Ts...>();


    For example, is_one_of<int, double, int, float> returns true, and is_one_of<int, double, std::string, bool, bool> returns false.



    My own implementation is



    template<class T1, class T2>
    bool is_one_of<T1, T2>()
    return std::is_same<T1, T2>;


    template<class T1, class T2, class... Ts>
    bool is_one_of<T1, T2, Ts...>()
    if (std::is_same<T1, T2>)
    return true;

    else
    return is_one_of<T1, Ts...>();




    This check seems common to me so I wonder if there's already such a function in the standard library.










    share|improve this question


























      32












      32








      32


      6






      I want to write a function to return true if T is one of Ts...



      template<class T, class... Ts>
      bool is_one_of<T, Ts...>();


      For example, is_one_of<int, double, int, float> returns true, and is_one_of<int, double, std::string, bool, bool> returns false.



      My own implementation is



      template<class T1, class T2>
      bool is_one_of<T1, T2>()
      return std::is_same<T1, T2>;


      template<class T1, class T2, class... Ts>
      bool is_one_of<T1, T2, Ts...>()
      if (std::is_same<T1, T2>)
      return true;

      else
      return is_one_of<T1, Ts...>();




      This check seems common to me so I wonder if there's already such a function in the standard library.










      share|improve this question
















      I want to write a function to return true if T is one of Ts...



      template<class T, class... Ts>
      bool is_one_of<T, Ts...>();


      For example, is_one_of<int, double, int, float> returns true, and is_one_of<int, double, std::string, bool, bool> returns false.



      My own implementation is



      template<class T1, class T2>
      bool is_one_of<T1, T2>()
      return std::is_same<T1, T2>;


      template<class T1, class T2, class... Ts>
      bool is_one_of<T1, T2, Ts...>()
      if (std::is_same<T1, T2>)
      return true;

      else
      return is_one_of<T1, Ts...>();




      This check seems common to me so I wonder if there's already such a function in the standard library.







      c++ variadic-templates






      share|improve this question















      share|improve this question













      share|improve this question




      share|improve this question








      edited Jun 25 at 8:56









      Uwe Keim

      28k32 gold badges141 silver badges223 bronze badges




      28k32 gold badges141 silver badges223 bronze badges










      asked Jun 23 at 0:00









      Yixing LiuYixing Liu

      5216 silver badges19 bronze badges




      5216 silver badges19 bronze badges




















          4 Answers
          4






          active

          oldest

          votes


















          36














          In your own implementation, one issue is that C++ doesn't allow partial specialization on function templates.



          You can use the fold expression (which is introduced in C++17) instead of recursive function call.



          template<class T1, class... Ts>
          constexpr bool is_one_of() noexcept


          If you are using C++11 where fold expression and std::disjunction are not available, you can implement is_one_of like this:



          template<class...> struct is_one_of: std::false_type ;
          template<class T1, class T2> struct is_one_of<T1, T2>: std::is_same<T1, T2> ;
          template<class T1, class T2, class... Ts> struct is_one_of<T1, T2, Ts...>: std::conditional<std::is_same<T1, T2>::value, std::is_same<T1, T2>, is_one_of<T1, Ts...>>::type ;





          share|improve this answer




















          • 2





            Could also be a variable and not a function :)

            – Rakete1111
            Jun 23 at 8:36


















          29














          You can also use std::disjunction to avoid unnecessary template instantiation:



          template <class T0, class... Ts>
          constexpr bool is_one_of = std::disjunction_v<std::is_same<T0, Ts>...>;


          After a matching type is found, the remaining templates are not instantiated. In contrast, a fold expression instantiates all of them. This can make a significant difference in compile time depending on your use case.






          share|improve this answer

























          • Actually, OP's implementation is very similar to the implementation of std::disjunction

            – Shaoyu Chen
            Jun 23 at 1:22






          • 3





            @ShaoyuChen Yep. The idea is that "there's already such a function in the standard library" as OP wondered

            – L. F.
            Jun 23 at 1:22


















          10














          Check if type T is among parameter pack Ts:



          template<class T0, class... Ts>
          constexpr bool is_one_of = (std::is_same<T0, Ts>||...);


          template variable.



          Alternative:



          template<class T0, class... Ts>
          constexpr std::integral_constant<bool,(std::is_same<T0, Ts>||...)> is_one_of = ;


          Which has subtle differences.






          share|improve this answer
































            2














            The other answers show several correct solutions to solve this specific problem in a clean and concise way. Here is a solution that is not recommended for this specific problem, but demonstrates an alternate technique: In constexpr functions you can use plain for loops and simple logic in order to compute results at compile time. This allows to get rid of the recursion and the attempted partial template specialization of OP's code.



            #include <initializer_list>
            #include <type_traits>

            template<class T, class... Ts>
            constexpr bool is_one_of()
            bool ret = false;

            for(bool is_this_one : std::is_same<T, Ts>::value...) = is_this_one;// alternative style: `if(is_this_one) return true;`


            return ret;


            static_assert(is_one_of<int, double, int, float>(), "");
            static_assert(!is_one_of<int, double, char, bool, bool>(), "");


            Requires at least C++14.






            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%2f56720024%2fhow-can-i-check-type-t-is-among-parameter-pack-ts%23new-answer', 'question_page');

              );

              Post as a guest















              Required, but never shown

























              4 Answers
              4






              active

              oldest

              votes








              4 Answers
              4






              active

              oldest

              votes









              active

              oldest

              votes






              active

              oldest

              votes









              36














              In your own implementation, one issue is that C++ doesn't allow partial specialization on function templates.



              You can use the fold expression (which is introduced in C++17) instead of recursive function call.



              template<class T1, class... Ts>
              constexpr bool is_one_of() noexcept


              If you are using C++11 where fold expression and std::disjunction are not available, you can implement is_one_of like this:



              template<class...> struct is_one_of: std::false_type ;
              template<class T1, class T2> struct is_one_of<T1, T2>: std::is_same<T1, T2> ;
              template<class T1, class T2, class... Ts> struct is_one_of<T1, T2, Ts...>: std::conditional<std::is_same<T1, T2>::value, std::is_same<T1, T2>, is_one_of<T1, Ts...>>::type ;





              share|improve this answer




















              • 2





                Could also be a variable and not a function :)

                – Rakete1111
                Jun 23 at 8:36















              36














              In your own implementation, one issue is that C++ doesn't allow partial specialization on function templates.



              You can use the fold expression (which is introduced in C++17) instead of recursive function call.



              template<class T1, class... Ts>
              constexpr bool is_one_of() noexcept


              If you are using C++11 where fold expression and std::disjunction are not available, you can implement is_one_of like this:



              template<class...> struct is_one_of: std::false_type ;
              template<class T1, class T2> struct is_one_of<T1, T2>: std::is_same<T1, T2> ;
              template<class T1, class T2, class... Ts> struct is_one_of<T1, T2, Ts...>: std::conditional<std::is_same<T1, T2>::value, std::is_same<T1, T2>, is_one_of<T1, Ts...>>::type ;





              share|improve this answer




















              • 2





                Could also be a variable and not a function :)

                – Rakete1111
                Jun 23 at 8:36













              36












              36








              36







              In your own implementation, one issue is that C++ doesn't allow partial specialization on function templates.



              You can use the fold expression (which is introduced in C++17) instead of recursive function call.



              template<class T1, class... Ts>
              constexpr bool is_one_of() noexcept


              If you are using C++11 where fold expression and std::disjunction are not available, you can implement is_one_of like this:



              template<class...> struct is_one_of: std::false_type ;
              template<class T1, class T2> struct is_one_of<T1, T2>: std::is_same<T1, T2> ;
              template<class T1, class T2, class... Ts> struct is_one_of<T1, T2, Ts...>: std::conditional<std::is_same<T1, T2>::value, std::is_same<T1, T2>, is_one_of<T1, Ts...>>::type ;





              share|improve this answer















              In your own implementation, one issue is that C++ doesn't allow partial specialization on function templates.



              You can use the fold expression (which is introduced in C++17) instead of recursive function call.



              template<class T1, class... Ts>
              constexpr bool is_one_of() noexcept


              If you are using C++11 where fold expression and std::disjunction are not available, you can implement is_one_of like this:



              template<class...> struct is_one_of: std::false_type ;
              template<class T1, class T2> struct is_one_of<T1, T2>: std::is_same<T1, T2> ;
              template<class T1, class T2, class... Ts> struct is_one_of<T1, T2, Ts...>: std::conditional<std::is_same<T1, T2>::value, std::is_same<T1, T2>, is_one_of<T1, Ts...>>::type ;






              share|improve this answer














              share|improve this answer



              share|improve this answer








              edited Jun 23 at 23:29

























              answered Jun 23 at 0:36









              Shaoyu ChenShaoyu Chen

              3361 silver badge5 bronze badges




              3361 silver badge5 bronze badges







              • 2





                Could also be a variable and not a function :)

                – Rakete1111
                Jun 23 at 8:36












              • 2





                Could also be a variable and not a function :)

                – Rakete1111
                Jun 23 at 8:36







              2




              2





              Could also be a variable and not a function :)

              – Rakete1111
              Jun 23 at 8:36





              Could also be a variable and not a function :)

              – Rakete1111
              Jun 23 at 8:36











              29














              You can also use std::disjunction to avoid unnecessary template instantiation:



              template <class T0, class... Ts>
              constexpr bool is_one_of = std::disjunction_v<std::is_same<T0, Ts>...>;


              After a matching type is found, the remaining templates are not instantiated. In contrast, a fold expression instantiates all of them. This can make a significant difference in compile time depending on your use case.






              share|improve this answer

























              • Actually, OP's implementation is very similar to the implementation of std::disjunction

                – Shaoyu Chen
                Jun 23 at 1:22






              • 3





                @ShaoyuChen Yep. The idea is that "there's already such a function in the standard library" as OP wondered

                – L. F.
                Jun 23 at 1:22















              29














              You can also use std::disjunction to avoid unnecessary template instantiation:



              template <class T0, class... Ts>
              constexpr bool is_one_of = std::disjunction_v<std::is_same<T0, Ts>...>;


              After a matching type is found, the remaining templates are not instantiated. In contrast, a fold expression instantiates all of them. This can make a significant difference in compile time depending on your use case.






              share|improve this answer

























              • Actually, OP's implementation is very similar to the implementation of std::disjunction

                – Shaoyu Chen
                Jun 23 at 1:22






              • 3





                @ShaoyuChen Yep. The idea is that "there's already such a function in the standard library" as OP wondered

                – L. F.
                Jun 23 at 1:22













              29












              29








              29







              You can also use std::disjunction to avoid unnecessary template instantiation:



              template <class T0, class... Ts>
              constexpr bool is_one_of = std::disjunction_v<std::is_same<T0, Ts>...>;


              After a matching type is found, the remaining templates are not instantiated. In contrast, a fold expression instantiates all of them. This can make a significant difference in compile time depending on your use case.






              share|improve this answer















              You can also use std::disjunction to avoid unnecessary template instantiation:



              template <class T0, class... Ts>
              constexpr bool is_one_of = std::disjunction_v<std::is_same<T0, Ts>...>;


              After a matching type is found, the remaining templates are not instantiated. In contrast, a fold expression instantiates all of them. This can make a significant difference in compile time depending on your use case.







              share|improve this answer














              share|improve this answer



              share|improve this answer








              edited Jun 24 at 8:25

























              answered Jun 23 at 1:00









              L. F.L. F.

              4,2033 gold badges16 silver badges40 bronze badges




              4,2033 gold badges16 silver badges40 bronze badges












              • Actually, OP's implementation is very similar to the implementation of std::disjunction

                – Shaoyu Chen
                Jun 23 at 1:22






              • 3





                @ShaoyuChen Yep. The idea is that "there's already such a function in the standard library" as OP wondered

                – L. F.
                Jun 23 at 1:22

















              • Actually, OP's implementation is very similar to the implementation of std::disjunction

                – Shaoyu Chen
                Jun 23 at 1:22






              • 3





                @ShaoyuChen Yep. The idea is that "there's already such a function in the standard library" as OP wondered

                – L. F.
                Jun 23 at 1:22
















              Actually, OP's implementation is very similar to the implementation of std::disjunction

              – Shaoyu Chen
              Jun 23 at 1:22





              Actually, OP's implementation is very similar to the implementation of std::disjunction

              – Shaoyu Chen
              Jun 23 at 1:22




              3




              3





              @ShaoyuChen Yep. The idea is that "there's already such a function in the standard library" as OP wondered

              – L. F.
              Jun 23 at 1:22





              @ShaoyuChen Yep. The idea is that "there's already such a function in the standard library" as OP wondered

              – L. F.
              Jun 23 at 1:22











              10














              Check if type T is among parameter pack Ts:



              template<class T0, class... Ts>
              constexpr bool is_one_of = (std::is_same<T0, Ts>||...);


              template variable.



              Alternative:



              template<class T0, class... Ts>
              constexpr std::integral_constant<bool,(std::is_same<T0, Ts>||...)> is_one_of = ;


              Which has subtle differences.






              share|improve this answer





























                10














                Check if type T is among parameter pack Ts:



                template<class T0, class... Ts>
                constexpr bool is_one_of = (std::is_same<T0, Ts>||...);


                template variable.



                Alternative:



                template<class T0, class... Ts>
                constexpr std::integral_constant<bool,(std::is_same<T0, Ts>||...)> is_one_of = ;


                Which has subtle differences.






                share|improve this answer



























                  10












                  10








                  10







                  Check if type T is among parameter pack Ts:



                  template<class T0, class... Ts>
                  constexpr bool is_one_of = (std::is_same<T0, Ts>||...);


                  template variable.



                  Alternative:



                  template<class T0, class... Ts>
                  constexpr std::integral_constant<bool,(std::is_same<T0, Ts>||...)> is_one_of = ;


                  Which has subtle differences.






                  share|improve this answer















                  Check if type T is among parameter pack Ts:



                  template<class T0, class... Ts>
                  constexpr bool is_one_of = (std::is_same<T0, Ts>||...);


                  template variable.



                  Alternative:



                  template<class T0, class... Ts>
                  constexpr std::integral_constant<bool,(std::is_same<T0, Ts>||...)> is_one_of = ;


                  Which has subtle differences.







                  share|improve this answer














                  share|improve this answer



                  share|improve this answer








                  edited Jun 23 at 2:23

























                  answered Jun 23 at 0:17









                  Yakk - Adam NevraumontYakk - Adam Nevraumont

                  194k21 gold badges211 silver badges401 bronze badges




                  194k21 gold badges211 silver badges401 bronze badges





















                      2














                      The other answers show several correct solutions to solve this specific problem in a clean and concise way. Here is a solution that is not recommended for this specific problem, but demonstrates an alternate technique: In constexpr functions you can use plain for loops and simple logic in order to compute results at compile time. This allows to get rid of the recursion and the attempted partial template specialization of OP's code.



                      #include <initializer_list>
                      #include <type_traits>

                      template<class T, class... Ts>
                      constexpr bool is_one_of()
                      bool ret = false;

                      for(bool is_this_one : std::is_same<T, Ts>::value...) = is_this_one;// alternative style: `if(is_this_one) return true;`


                      return ret;


                      static_assert(is_one_of<int, double, int, float>(), "");
                      static_assert(!is_one_of<int, double, char, bool, bool>(), "");


                      Requires at least C++14.






                      share|improve this answer





























                        2














                        The other answers show several correct solutions to solve this specific problem in a clean and concise way. Here is a solution that is not recommended for this specific problem, but demonstrates an alternate technique: In constexpr functions you can use plain for loops and simple logic in order to compute results at compile time. This allows to get rid of the recursion and the attempted partial template specialization of OP's code.



                        #include <initializer_list>
                        #include <type_traits>

                        template<class T, class... Ts>
                        constexpr bool is_one_of()
                        bool ret = false;

                        for(bool is_this_one : std::is_same<T, Ts>::value...) = is_this_one;// alternative style: `if(is_this_one) return true;`


                        return ret;


                        static_assert(is_one_of<int, double, int, float>(), "");
                        static_assert(!is_one_of<int, double, char, bool, bool>(), "");


                        Requires at least C++14.






                        share|improve this answer



























                          2












                          2








                          2







                          The other answers show several correct solutions to solve this specific problem in a clean and concise way. Here is a solution that is not recommended for this specific problem, but demonstrates an alternate technique: In constexpr functions you can use plain for loops and simple logic in order to compute results at compile time. This allows to get rid of the recursion and the attempted partial template specialization of OP's code.



                          #include <initializer_list>
                          #include <type_traits>

                          template<class T, class... Ts>
                          constexpr bool is_one_of()
                          bool ret = false;

                          for(bool is_this_one : std::is_same<T, Ts>::value...) = is_this_one;// alternative style: `if(is_this_one) return true;`


                          return ret;


                          static_assert(is_one_of<int, double, int, float>(), "");
                          static_assert(!is_one_of<int, double, char, bool, bool>(), "");


                          Requires at least C++14.






                          share|improve this answer















                          The other answers show several correct solutions to solve this specific problem in a clean and concise way. Here is a solution that is not recommended for this specific problem, but demonstrates an alternate technique: In constexpr functions you can use plain for loops and simple logic in order to compute results at compile time. This allows to get rid of the recursion and the attempted partial template specialization of OP's code.



                          #include <initializer_list>
                          #include <type_traits>

                          template<class T, class... Ts>
                          constexpr bool is_one_of()
                          bool ret = false;

                          for(bool is_this_one : std::is_same<T, Ts>::value...) = is_this_one;// alternative style: `if(is_this_one) return true;`


                          return ret;


                          static_assert(is_one_of<int, double, int, float>(), "");
                          static_assert(!is_one_of<int, double, char, bool, bool>(), "");


                          Requires at least C++14.







                          share|improve this answer














                          share|improve this answer



                          share|improve this answer








                          edited Jun 25 at 10:21

























                          answered Jun 25 at 8:55









                          JuliusJulius

                          1,3686 silver badges13 bronze badges




                          1,3686 silver badges13 bronze badges



























                              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.




                              draft saved


                              draft discarded














                              StackExchange.ready(
                              function ()
                              StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f56720024%2fhow-can-i-check-type-t-is-among-parameter-pack-ts%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

                              Get product attribute by attribute group code in magento 2get product attribute by product attribute group in magento 2Magento 2 Log Bundle Product Data in List Page?How to get all product attribute of a attribute group of Default attribute set?Magento 2.1 Create a filter in the product grid by new attributeMagento 2 : Get Product Attribute values By GroupMagento 2 How to get all existing values for one attributeMagento 2 get custom attribute of a single product inside a pluginMagento 2.3 How to get all the Multi Source Inventory (MSI) locations collection in custom module?Magento2: how to develop rest API to get new productsGet product attribute by attribute group code ( [attribute_group_code] ) in magento 2

                              Category:9 (number) SubcategoriesMedia in category "9 (number)"Navigation menuUpload mediaGND ID: 4485639-8Library of Congress authority ID: sh85091979ReasonatorScholiaStatistics

                              Magento 2.3: How do i solve this, Not registered handle, on custom form?How can i rewrite TierPrice Block in Magento2magento 2 captcha not rendering if I override layout xmlmain.CRITICAL: Plugin class doesn't existMagento 2 : Problem while adding custom button order view page?Magento 2.2.5: Overriding Admin Controller sales/orderMagento 2.2.5: Add, Update and Delete existing products Custom OptionsMagento 2.3 : File Upload issue in UI Component FormMagento2 Not registered handleHow to configured Form Builder Js in my custom magento 2.3.0 module?Magento 2.3. How to create image upload field in an admin form