C++编程实践——标准库overloaded实现分析

一、overloaded的应用

在前面的文章中,不管是std::variant还是visit等的分析中,都提到过overloaded这个STL中的例子。当时觉得很简单的便没有分析说明。可后来在写模板技术时,才发觉可能对于很多模板技术初学者或想深入学习的模板技术爱好者,可能还是要把它们分析清楚为好。下面是cppreference中的overloaded的应用:

#include <variant>
#include <iostream>
#include <type_traits>
#include <iomanip>
#include <vector>
 
 
template<class T> struct always_false : std::false_type {};
 
using var_t = std::variant<int, long, double, std::string>;
 
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
 
int main() {
    std::vector<var_t> vec = {10, 15l, 1.5, "hello"};
    for(auto& v: vec) {
        // void visitor, only called for side-effects
        std::visit([](auto&& arg){std::cout << arg;}, v);
 
        // value-returning visitor. A common idiom is to return another variant
        var_t w = std::visit([](auto&& arg) -> var_t {return arg + arg;}, v);
 
        std::cout << ". After doubling, variant holds ";
        // type-matching visitor: can also be a class with 4 overloaded operator()'s
        std::visit([](auto&& arg) {
            using T = std::decay_t<decltype(arg)>;
            if constexpr (std::is_same_v<T, int>)
                std::cout << "int with value " << arg << '\n';
            else if constexpr (std::is_same_v<T, long>)
                std::cout << "long with value " << arg << '\n';
            else if constexpr (std::is_same_v<T, double>)
                std::cout << "double with value " << arg << '\n';
            else if constexpr (std::is_same_v<T, std::string>)
                std::cout << "std::string with value " << std::quoted(arg) << '\n';
            else 
                static_assert(always_false<T>::value, "non-exhaustive visitor!");
        }, w);
    }
 
    for (auto& v: vec) {
        std::visit(overloaded {
            [](int arg) { std::cout << arg << ' '; },
            [](long arg) { std::cout << arg << ' '; },
            [](double arg) { std::cout << arg << ' '; },
            [](const std::string& arg) { std::cout << std::quoted(arg) << ' '; },
        }, v);
    }
}

结合前面才刚刚分析过的std::visit实现分析,其实很容易理解上面的代码。通过decltype获取参数arg的数据类型,为了确保类型的处理简单,利用std::decay_t来“退化”类型的相关限定符及修饰符等,从而得到最初的普通类型。然后利用if constexpr的方式达到在编译期的条件分支处理。
至于std::false_type,则是用于元编程中的bool处理。在前面的模板技术中进行过分析和应用举例,大家有兴趣可以翻看一下。下面重点对overloaded结构体进行分析说明。

二、分析说明

要想明白overloaded是怎么应用的,最好把模板的最终展开看一下。其实,有了overloaded的展开后的形式,对大多数有一定技术经验的开发者来说,已经没有什么太大的问题了。下面将上面overloaded例程的代码展开结果贴上来:


template<class T>
struct always_false : public std::integral_constant<bool, false>
{
};


using var_t = std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > >;

template<class ... Ts>
struct overloaded : public Ts...
{
  using Ts::operator()...;
};

/* First instantiated from: insights.cpp:42 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
struct overloaded<__lambda_43_13, __lambda_44_13, __lambda_45_13, __lambda_46_13> : public __lambda_43_13, public __lambda_44_13, public __lambda_45_13, public __lambda_46_13
{
  using __lambda_43_13::operator();
  // inline /*constexpr */ void ::operator()(int arg) const;
  
  using __lambda_44_13::operator();
  // inline /*constexpr */ void ::operator()(long arg) const;
  
  using __lambda_45_13::operator();
  // inline /*constexpr */ void ::operator()(double arg) const;
  
  using __lambda_46_13::operator();
  // inline /*constexpr */ void ::operator()(const std::basic_string<char, std::char_traits<char>, std::allocator<char> > & arg) const;
  
};

#endif
template<class ... Ts>
overloaded(Ts...) -> overloaded<Ts...>;

/* First instantiated from: insights.cpp:42 */
#ifdef INSIGHTS_USE_TEMPLATE
template<>
overloaded(__lambda_43_13 __0, __lambda_44_13 __1, __lambda_45_13 __2, __lambda_46_13 __3) -> overloaded<__lambda_43_13, __lambda_44_13, __lambda_45_13, __lambda_46_13>;
#endif


int main()
{
  std::vector<std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > > > > vec = std::vector<std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > > > >{std::initializer_list<std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > > >{std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > >(10), std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > >(15L), std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > >(1.5), std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > >("hello")}, std::allocator<std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > > >()};
  {
    std::vector<std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > > > > & __range1 = vec;
    __gnu_cxx::__normal_iterator<std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > > *, std::vector<std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > > > > > __begin1 = __range1.begin();
    __gnu_cxx::__normal_iterator<std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > > *, std::vector<std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > > > > > __end1 = __range1.end();
    for(; !__gnu_cxx::operator==(__begin1, __end1); __begin1.operator++()) {
      std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > > & v = __begin1.operator*();
            
      class __lambda_19_20
      {
        public: 
        template<class type_parameter_0_0>
        inline /*constexpr */ auto operator()(type_parameter_0_0 && arg) const
        {
          std::cout << arg;
        }
        
        #ifdef INSIGHTS_USE_TEMPLATE
        template<>
        inline /*constexpr */ void operator()<int &>(int & arg) const
        {
          std::cout.operator<<(arg);
        }
        #endif
        
        
        #ifdef INSIGHTS_USE_TEMPLATE
        template<>
        inline /*constexpr */ void operator()<long &>(long & arg) const
        {
          std::cout.operator<<(arg);
        }
        #endif
        
        
        #ifdef INSIGHTS_USE_TEMPLATE
        template<>
        inline /*constexpr */ void operator()<double &>(double & arg) const
        {
          std::cout.operator<<(arg);
        }
        #endif
        
        
        #ifdef INSIGHTS_USE_TEMPLATE
        template<>
        inline /*constexpr */ void operator()<std::basic_string<char, std::char_traits<char>, std::allocator<char> > &>(std::basic_string<char, std::char_traits<char>, std::allocator<char> > & arg) const
        {
          std::operator<<(std::cout, arg);
        }
        #endif
        
        private: 
        template<class type_parameter_0_0>
        static inline /*constexpr */ auto __invoke(type_parameter_0_0 && arg)
        {
          return __lambda_19_20{}.operator()<type_parameter_0_0>(arg);
        }
        
        public:
        // /*constexpr */ __lambda_19_20() = default;
        
      };
      
      std::visit(__lambda_19_20{}, v);
                  
      class __lambda_22_30
      {
        public: 
        template<class type_parameter_0_0>
        inline /*constexpr */ std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > > operator()(type_parameter_0_0 && arg) const
        {
          return arg + arg;
        }
        
        /* First instantiated from: invoke.h:61 */
        #ifdef INSIGHTS_USE_TEMPLATE
        template<>
        inline /*constexpr */ std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > > operator()<int &>(int & arg) const
        {
          return std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > >(arg + arg);
        }
        #endif
        
        
        /* First instantiated from: invoke.h:61 */
        #ifdef INSIGHTS_USE_TEMPLATE
        template<>
        inline /*constexpr */ std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > > operator()<long &>(long & arg) const
        {
          return std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > >(arg + arg);
        }
        #endif
        
        
        /* First instantiated from: invoke.h:61 */
        #ifdef INSIGHTS_USE_TEMPLATE
        template<>
        inline /*constexpr */ std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > > operator()<double &>(double & arg) const
        {
          return std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > >(arg + arg);
        }
        #endif
        
        
        /* First instantiated from: invoke.h:61 */
        #ifdef INSIGHTS_USE_TEMPLATE
        template<>
        inline /*constexpr */ std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > > operator()<std::basic_string<char, std::char_traits<char>, std::allocator<char> > &>(std::basic_string<char, std::char_traits<char>, std::allocator<char> > & arg) const
        {
          return std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > >(std::operator+(arg, arg));
        }
        #endif
        
        private: 
        template<class type_parameter_0_0>
        static inline /*constexpr */ std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > > __invoke(type_parameter_0_0 && arg)
        {
          return __lambda_22_30{}.operator()<type_parameter_0_0>(arg);
        }
        
        public:
        // /*constexpr */ __lambda_22_30() = default;
        
      };
      
      std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > > w = std::visit(__lambda_22_30{}, v);
      std::operator<<(std::cout, ". After doubling, variant holds ");
            
      class __lambda_26_20
      {
        public: 
        template<class type_parameter_0_0>
        inline /*constexpr */ auto operator()(type_parameter_0_0 && arg) const
        {
          using T = std::decay_t<decltype(arg)>;
          if constexpr(std::is_same_v<T, int>) {
            (std::operator<<(std::cout, "int with value ") << arg) << '\n';
          } else /* constexpr */ {
            if constexpr(std::is_same_v<T, long>) {
              (std::operator<<(std::cout, "long with value ") << arg) << '\n';
            } else /* constexpr */ {
              if constexpr(std::is_same_v<T, double>) {
                (std::operator<<(std::cout, "double with value ") << arg) << '\n';
              } else /* constexpr */ {
                if constexpr(std::is_same_v<T, std::basic_string<char, std::char_traits<char>, std::allocator<char> > >) {
                  (std::operator<<(std::cout, "std::string with value ") << std::quoted(arg)) << '\n';
                } else /* constexpr */ {
                  /* PASSED: static_assert(always_false<T>::value, "non-exhaustive visitor!"); */
                  ;
                } 
                
              } 
              
            } 
            
          } 
          
        }
        
        #ifdef INSIGHTS_USE_TEMPLATE
        template<>
        inline /*constexpr */ void operator()<int &>(int & arg) const
        {
          using T = std::decay_t<int &>;
          if constexpr(true) {
            std::operator<<(std::operator<<(std::cout, "int with value ").operator<<(arg), '\n');
          } else /* constexpr */ {
          } 
          
        }
        #endif
        
        
        #ifdef INSIGHTS_USE_TEMPLATE
        template<>
        inline /*constexpr */ void operator()<long &>(long & arg) const
        {
          using T = std::decay_t<long &>;
          if constexpr(false) {
          } else /* constexpr */ {
            if constexpr(true) {
              std::operator<<(std::operator<<(std::cout, "long with value ").operator<<(arg), '\n');
            } else /* constexpr */ {
            } 
            
          } 
          
        }
        #endif
        
        
        #ifdef INSIGHTS_USE_TEMPLATE
        template<>
        inline /*constexpr */ void operator()<double &>(double & arg) const
        {
          using T = std::decay_t<double &>;
          if constexpr(false) {
          } else /* constexpr */ {
            if constexpr(false) {
            } else /* constexpr */ {
              if constexpr(true) {
                std::operator<<(std::operator<<(std::cout, "double with value ").operator<<(arg), '\n');
              } else /* constexpr */ {
              } 
              
            } 
            
          } 
          
        }
        #endif
        
        
        #ifdef INSIGHTS_USE_TEMPLATE
        template<>
        inline /*constexpr */ void operator()<std::basic_string<char, std::char_traits<char>, std::allocator<char> > &>(std::basic_string<char, std::char_traits<char>, std::allocator<char> > & arg) const
        {
          using T = std::decay_t<std::basic_string<char, std::char_traits<char>, std::allocator<char> > &>;
          if constexpr(false) {
          } else /* constexpr */ {
            if constexpr(false) {
            } else /* constexpr */ {
              if constexpr(false) {
              } else /* constexpr */ {
                if constexpr(true) {
                  std::operator<<(std::__detail::operator<<(std::operator<<(std::cout, "std::string with value "), std::quoted(arg, char('"'), char('\\'))), '\n');
                } else /* constexpr */ {
                } 
                
              } 
              
            } 
            
          } 
          
        }
        #endif
        
        private: 
        template<class type_parameter_0_0>
        static inline /*constexpr */ auto __invoke(type_parameter_0_0 && arg)
        {
          return __lambda_26_20{}.operator()<type_parameter_0_0>(arg);
        }
        
        public:
        // /*constexpr */ __lambda_26_20() = default;
        
      };
      
      std::visit(__lambda_26_20{}, w);
    }
    
  }
  {
    std::vector<std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > > > > & __range1 = vec;
    __gnu_cxx::__normal_iterator<std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > > *, std::vector<std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > > > > > __begin1 = __range1.begin();
    __gnu_cxx::__normal_iterator<std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > > *, std::vector<std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > >, std::allocator<std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > > > > > __end1 = __range1.end();
    for(; !__gnu_cxx::operator==(__begin1, __end1); __begin1.operator++()) {
      std::variant<int, long, double, std::basic_string<char, std::char_traits<char>, std::allocator<char> > > & v = __begin1.operator*();
            
      class __lambda_43_13
      {
        public: 
        inline /*constexpr */ void operator()(int arg) const
        {
          std::operator<<(std::cout.operator<<(arg), ' ');
        }
        
        using retType_43_13 = void (*)(int);
        inline constexpr operator retType_43_13 () const noexcept
        {
          return __invoke;
        };
        
        private: 
        static inline /*constexpr */ void __invoke(int arg)
        {
          __lambda_43_13{}.operator()(arg);
        }
        
        public: 
        // inline /*constexpr */ __lambda_43_13(__lambda_43_13 &&) noexcept = default;
        // /*constexpr */ __lambda_43_13() = default;
        
      };
      
      
      class __lambda_44_13
      {
        public: 
        inline /*constexpr */ void operator()(long arg) const
        {
          std::operator<<(std::cout.operator<<(arg), ' ');
        }
        
        using retType_44_13 = void (*)(long);
        inline constexpr operator retType_44_13 () const noexcept
        {
          return __invoke;
        };
        
        private: 
        static inline /*constexpr */ void __invoke(long arg)
        {
          __lambda_44_13{}.operator()(arg);
        }
        
        public: 
        // inline /*constexpr */ __lambda_44_13(__lambda_44_13 &&) noexcept = default;
        // /*constexpr */ __lambda_44_13() = default;
        
      };
      
      
      class __lambda_45_13
      {
        public: 
        inline /*constexpr */ void operator()(double arg) const
        {
          std::operator<<(std::cout.operator<<(arg), ' ');
        }
        
        using retType_45_13 = void (*)(double);
        inline constexpr operator retType_45_13 () const noexcept
        {
          return __invoke;
        };
        
        private: 
        static inline /*constexpr */ void __invoke(double arg)
        {
          __lambda_45_13{}.operator()(arg);
        }
        
        public: 
        // inline /*constexpr */ __lambda_45_13(__lambda_45_13 &&) noexcept = default;
        // /*constexpr */ __lambda_45_13() = default;
        
      };
      
      
      class __lambda_46_13
      {
        public: 
        inline /*constexpr */ void operator()(const std::basic_string<char, std::char_traits<char>, std::allocator<char> > & arg) const
        {
          std::operator<<(std::__detail::operator<<(std::cout, std::quoted(arg, char('"'), char('\\'))), ' ');
        }
        
        using retType_46_13 = void (*)(const std::string &);
        inline constexpr operator retType_46_13 () const noexcept
        {
          return __invoke;
        };
        
        private: 
        static inline /*constexpr */ void __invoke(const std::basic_string<char, std::char_traits<char>, std::allocator<char> > & arg)
        {
          __lambda_46_13{}.operator()(arg);
        }
        
        public: 
        // inline /*constexpr */ __lambda_46_13(__lambda_46_13 &&) noexcept = default;
        // /*constexpr */ __lambda_46_13() = default;
        
      };
      
      std::visit(overloaded{__lambda_43_13(__lambda_43_13{}), __lambda_44_13(__lambda_44_13{}), __lambda_45_13(__lambda_45_13{}), __lambda_46_13(__lambda_46_13{})}, v);
    }
    
  }
  return 0;
}

代码中template<class… Ts> struct overloaded,表示结构体(类)overloaded是一个可变参数模板类,其参数为可变长的参数包 Ts。这个可变参数包Ts可以是 T1, T2, … , Tn,那么这个声明在编译后可展开为:template<class T1, class T2, …, class Tn> struct overloaded。可看上面的展开说明中的“__lambda_43_13, __lambda_44_13, __lambda_45_13, __lambda_46_13”,这是不是和lambda表达式编译后转成的类有些类似。那就对了,正好对上主函数中的编译后展开的lambda表达式类型。
后面的struct overloaded : Ts…中的Ts…就可以顺手理解了,即展开的“struct overloaded:public __lambda_43_13, public __lambda_44_13”等。即表示类的基类为参数包 Ts内展开的所有参数类型。“using Ts::operator()…;”,这是一个运算符"()"的重载,它的展开为:

  using __lambda_43_13::operator();
  // inline /*constexpr */ void ::operator()(int arg) const;
  
  using __lambda_44_13::operator();
  // inline /*constexpr */ void ::operator()(long arg) const;
  
  using __lambda_45_13::operator();
  // inline /*constexpr */ void ::operator()(double arg) const;
  
  using __lambda_46_13::operator();
  // inline /*constexpr */ void ::operator()(const std::basic_string<char, std::char_traits<char>, std::allocator<char> > & arg) const;
  

当然这是一个变长的参数包处理,理论上是可以展开到“Tn::operator();”。这样,overloaded参数包Ts中的所有成员的“()”操作符都被处理完成。template<class… Ts> overloaded(Ts…) -> overloaded<Ts…>;这个对很多开发者可能比较生疏,它是用来帮助编译器如何对变参参数包进行展开的一种自动推导参数的模式即前面分析过的自定义的自动推断向导说明(CTAD中的一类)。C++17需要,C++20标准后不用再写了。
编译器通过这段推断指导,对overloaded构造器中的参数包Ts(比如int,float,char),则 overloaded的模板参数类型就是 Ts所包含的所有类型int,float,char。对应上面的代码中的具体类型(几个不同类型的lambda表达式,编译后会形成几个类)那么

overloaded {
            [](int arg) { std::cout << arg << ' '; },
            [](long arg) { std::cout << arg << ' '; },
            [](double arg) { std::cout << arg << ' '; },
            [](const std::string& arg) { std::cout << std::quoted(arg) << ' '; },
        }

则可推断出:

template<>
overloaded(__lambda_43_13 __0, __lambda_44_13 __1, __lambda_45_13 __2, __lambda_46_13 __3) -> overloaded<__lambda_43_13, __lambda_44_13, __lambda_45_13, __lambda_46_13>;

至于说为什么C++20为啥可以省略这个推断,那自然是标准的演进让编译器可以更准确的进行相关的推导罢了。另外之所以使用using declaration,目的是为了安全,防止出现重载时的歧义。

三、总结

记得最初学习C++技术时,一个特别简单的技术点往往能耗费数日。当时不比现在,网络和AI遍地铺开,当时想问人都不知道问谁。就如最初级的配置文件路径都不知道,后来问朋友才知道还需要这样做。所以就有了此篇文章的由来,希望能给有兴趣于模板技术的开发者一点点帮助,则不胜高兴。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值