【问题标题】:Template argument as function unnamed argument模板参数作为函数未命名参数
【发布时间】:2021-10-19 05:42:12
【问题描述】:

这个问题继续Non-static data members class deduction

这里是未命名的参数函数,我用来返回数据类型的std::string 表示

struct Boo {};
struct Foo {};

std::string class2str(const double) { return "Floating"; };
std::string class2str(const int) { return "Fixed Point"; };
std::string class2str(const Foo) { return "Class Foo"; };
std::string class2str(const Boo) { return "Class Boo"; };

int main(int argc, char* argv[]) 
{
    int    x_a;
    double x_b;
    Foo    F;
    Boo    B;
    std::cout << "x_a     :" << class2str(x_a) << std::endl;
    std::cout << "x_b     :" << class2str(x_b) << std::endl;
    std::cout << "Foo     :" << class2str(F) << std::endl;
    std::cout << "Boo     :" << class2str(B) << std::endl;
};

对于非静态成员的类型推导,我使用的是模板:

struct Foo { double A = 33; }

template<typename Class, typename MemType>
std::string class2str(MemType Class::* mData)
{
    return class2str(MemType{}); // Use of empty constructor
}

std::cout << "Foo::A  :" << class2str(&Foo::A) << std::endl;

但是这个模板需要创建一个带有空构造函数的对象,而它可能根本不存在

struct Boo 
{
    double A;
    Boo() = delete;
    Boo(int x) :A(x) {};
};

struct Foo 
{
    double A = 33;
    Boo    b{ 0 };
};

// Compilation error: use of deleted function ‘Boo::Boo()’
std::cout << "Boo::b  :" << class2str(&Foo::b) << std::endl;

如何在不调用空构造函数的情况下实现这个功能?

查看在线演示:https://onlinegdb.com/lpc5o8pUKy

【问题讨论】:

    标签: c++ class templates c++17 function-templates


    【解决方案1】:

    (当我开始写答案时,这个问题没有答案,但是当我要发布它时,我看到@Jarod42 的答案已经显示了标签调度方法。尽管如此,仍然发布了这个答案,因为它使用了删除主模板的完全特化方法略有不同,而不是非模板重载)


    您可以使用标签调度来委托调用:

    #include <iostream>
    
    struct Boo {
      double A;
      Boo() = delete;
      Boo(int x) : A(x){};
    };
    
    struct Foo {
      double A = 33;
      Boo b{0};
    };
    
    namespace detail {
    template <typename T> struct Tag {};
    
    template <typename T> std::string class2str_impl(Tag<T>) = delete;
    template <> std::string class2str_impl(Tag<double>) { return "Floating"; };
    template <> std::string class2str_impl(Tag<int>) { return "Fixed Point"; };
    template <> std::string class2str_impl(Tag<Foo>) { return "Class Foo"; };
    template <> std::string class2str_impl(Tag<Boo>) { return "Class Boo"; };
    
    } // namespace detail
    
    template <typename T> std::string class2str(T) {
      return class2str_impl(detail::Tag<T>{});
    }
    
    template <typename Class, typename MemType>
    std::string class2str(MemType Class::*) {
      return class2str_impl(detail::Tag<MemType>{});
    }
    
    int main() {
      int x_a{42};
      double x_b{4.2};
      Foo F{};
      Boo B{x_a};
    
      std::cout << "x_a     :" << class2str(x_a) << std::endl;
      std::cout << "x_b     :" << class2str(x_b) << std::endl;
      std::cout << "Foo     :" << class2str(F) << std::endl;
      std::cout << "Boo     :" << class2str(B) << std::endl;
      std::cout << "Boo::b  :" << class2str(&Foo::b) << std::endl;
    };
    

    class2str_impl 的主模板可以被删除(如上),或者实现给定类型没有映射字符串的自定义消息。

    【讨论】:

      【解决方案2】:

      您所有的重载当前都带有对象。你可以使用 type 代替,或者使用持有 type 的对象:

      template <typename T> struct Tag{};
      
      std::string class2str(Tag<double>){ return "Floating";};
      std::string class2str(Tag<int>){ return "Fixed Point";};
      std::string class2str(Tag<Foo>){ return "Class Foo";};
      std::string class2str(Tag<Boo>){ return "Class Boo";};
      
      
      template<typename Class, typename MemType>
      std::string class2str(Tag<MemType Class::*>)
      {
          return class2str(Tag<MemType> {});
      }
      

      有用法:

      int main(int argc, char *argv[]) {
          int    x_a;
          double x_b;
          Foo    F;
          Boo    B;
      
          std::cout<< "x_a     :" << class2str(Tag<decltype(x_a)>{}) <<std::endl;
          std::cout<< "x_b     :" << class2str(Tag<decltype(x_b)>{}) <<std::endl;
          std::cout<< "Foo     :" << class2str(Tag<decltype(F)>{}) <<std::endl;
          std::cout<< "Boo     :" << class2str(Tag<decltype(B)>{}) <<std::endl;
          // or
          std::cout<< "int     :" << class2str(Tag<int>{}) <<std::endl;
      };
      

      【讨论】:

        【解决方案3】:

        与其他答案中谈到的标签调度和专业化技术不同,这里是使用 constexpr if 的不同方法。

        • 首先,我们使用 trait (mem_type) 从成员指针中找到成员的类型

        • 其次,我们编写一个内部辅助函数(即helper::class2str()),它使用编译时间类型检查并丢弃错误分支(即if constexpr),这样我们就可以正确返回数据类型表示为const char*文字(因为我们可以创建函数constexpr)!

        • 最后,我们将拥有主要的class2str(),它实际上检查模板参数类型是否为成员指针,并再次使用if constexpr 进行分支。如果模板类型是成员指针,我们使用 trait mem_type 获取成员类型并将其传递给 helper::class2str()

        #include <type_traits>  // std::is_same_v, std::is_member_pointer_v
        
        // trait to get the member type
        template<typename Class> struct mem_type {};
        template<typename MemType, typename Class> struct mem_type<MemType Class::*> {
            using type = MemType;
        };
        // alias for mem_type<T>
        template<typename Type> using mem_type_t = typename mem_type<Type>::type;
        
        namespace helper
        {
            template<typename Type> constexpr auto class2str() noexcept
            {
                if constexpr (std::is_same_v<Type, int>)           return "Fixed Point";
                else if constexpr (std::is_same_v<Type, double>)   return "Floating";
                else if constexpr (std::is_same_v<Type, Boo>)      return "Class Boo";
                else if constexpr (std::is_same_v<Type, Foo>)      return "Class Foo";
            }
        }
        
        template<typename Type>
        std::string class2str()
        {
            if constexpr (std::is_member_pointer_v<Type>) 
                return helper::class2str<mem_type_t<Type>>();
            else 
                return helper::class2str<Type>(); 
        }
        

        现在你可以像这样使用它:

        std::cout << "x_a     :" << class2str<int>() << '\n';
        std::cout << "x_b     :" << class2str<double>() << '\n';
        std::cout << "Boo::b  :" << class2str<decltype(&Boo::A)>() << '\n';
        std::cout << "Foo::b  :" << class2str<decltype(&Foo::b)>() << '\n';
        

        这里是(the complete demo)

        【讨论】:

          【解决方案4】:

          制作一个类模板并将其专门用于各种类型:

          template <typename T> struct TypeNameHelper {};
          template <> struct TypeNameHelper<int> { static constexpr std::string_view name = "Integer"; };
          template <> struct TypeNameHelper<float> { static constexpr std::string_view name = "Real"; };
          

          如果需要,我还将添加一个变量模板以获得更短的语法并预处理类型:

          template <typename T>
          inline constexpr std::string_view TypeName = TypeNameHelper<std::remove_cvref_t<T>>::name;
          

          那么你可以这样做:

          std::cout << TypeName<int> << '\n';
          

          【讨论】:

            【解决方案5】:

            一个想法可能是使用函数参数来调用只需要模板参数的函数重载:

            struct Boo {
                double A;
                Boo()= delete;
                Boo(int x) :A(x){};
            };
            
            struct Foo {
                double A = 33;
                Boo    b{0};
            };
            
            #include <type_traits>
            #include <utility>
            
            template<class T> std::string class2str(); // primary
            // specializations
            template<> std::string class2str<double>(){ return "Floating"; };
            template<> std::string class2str<int>(){ return "Fixed Point"; };
            template<> std::string class2str<Foo>(){ return "Class Foo"; };
            template<> std::string class2str<Boo>(){ return "Class Boo"; };
            
            // taking by argument
            template<class T>
            std::string class2str(const T&) { 
                return class2str<std::remove_cv_t<std::remove_reference_t<T>>>();
            }
            
            // class member by argument
            template<typename Class, typename MemType>
            std::string class2str(MemType Class::*) {
                return class2str<std::remove_cv_t<std::remove_reference_t<MemType>>>();
            }
            
            int main() {
                int    x_a;
                double x_b;
                Foo    F;
                Boo    B{1};
            
                std::cout<< "x_a     :" << class2str(x_a) <<std::endl;
                std::cout<< "x_b     :" << class2str(x_b) <<std::endl;
                std::cout<< "Foo     :" << class2str(F) <<std::endl;
                std::cout<< "Boo     :" << class2str(B) <<std::endl;
                std::cout<< "Foo::A  :" << class2str(&Foo::A) <<std::endl;
                std::cout<< "Foo::b  :" << class2str(&Foo::b) <<std::endl;
                std::cout<< "Boo::A  :" << class2str(&Boo::A) <<std::endl;
            };
            

            输出:

            x_a     :Fixed Point
            x_b     :Floating
            Foo     :Class Foo
            Boo     :Class Boo
            Foo::A  :Floating
            Foo::b  :Class Boo
            Boo::A  :Floating
            

            【讨论】:

              【解决方案6】:

              标准库中常用的技巧是使用declval
              它是为这个确切的用例设计的。 或者更简单

              template<typename Class, typename MemType>  
              std::string class2str(MemType Class::*){  
                  return class2str(*reinterpret_cast<MemType*>(0)); // Never use the value, it's null  
              }  
              

              【讨论】:

              • "从不使用该值" ?您正在使用该值将其传递给class2str。我几乎可以肯定这是未定义的行为
              • 如果存在任何带有 NAMED 参数的函数。由于没有任何版本的函数实际命名参数(因此不能使用该值),这很好。
              • 也就是说,它确实需要代码维护者知道这一点。您可能会批评修复“脆弱”,但它不是 U/B。匿名(未命名)参数是隐含的“未使用”,因此没有行为,无论是否未定义。
              • 您是否尝试过实际调用此class2str 函数? GCC 和 Clang 都有段错误。
              • Arggg....我今天过得很糟糕...你说得对。当它被推入堆栈时,它必须尊重它。这取决于优化设置是否有效。
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2016-11-18
              • 2021-01-08
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多