V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
iqoo
V2EX  ›  程序员

关于 C++ 模板一个问题

  •  
  •   iqoo · 2023-08-11 16:07:37 +08:00 · 1617 次点击
    这是一个创建于 472 天前的主题,其中的信息可能已经有所发展或是发生改变。

    如下代码,log 函数接受 Base 类及其子类 Str 、Num 。调用时无需手动指定类型,传入 char* 可自动生成 Str 类,传入 int 可自动生成 Num 类。

    struct Base {
    	void print() {
    	}
    };
    
    struct Str : Base {
    	Str(const char* s) {}
    };
    
    struct Num : Base {
    	Num(int v) {}
    };
    
    template<typename T>
    void log(T obj) {
    	obj.print();
    }
    
    int main() {
    	log("hello");
    	log(123);
    }
    

    如何让模板根据传入的类型推算出用什么类?例如传入 char* 时选择 Str 类,传入 int 时选择 Num 类。

    第 1 条附言  ·  2023-08-11 17:41:43 +08:00
    感谢回答。

    如果能直接指定模板参数的类型集合就好了,例如 Str | Num ,编译器按照依次尝试去构造,这样写起来会方便些。上述只是个伪代码,实际有多个类,如果每种类的构造参数都自己拿出来写一遍,还是有些累赘。

    不知用 c++ concept 约束模块类型是否有效?(编译器使用 C++23 )

    如果 log 函数只有一个参数用 overload 更方便,几种类型写一遍就可以了。实际的 log 函数是可变参数,每个参数都可以是 Str | Num | Base 。
    12 条回复    2023-08-11 22:40:25 +08:00
    NessajCN
        1
    NessajCN  
       2023-08-11 16:22:17 +08:00
    void log(T param)
    {
    if constexpr (std::is_same_v<T, const char*>)
    Str obj(param);
    obj.print();
    else if constexpr (std::is_same_v<T, int>)
    Num obj(param);
    obj.print();
    else
    std::cout << "???" << std::endl;
    }
    geelaw
        2
    geelaw  
       2023-08-11 16:27:13 +08:00   ❤️ 2
    楼主的代码明明是 log 可以接受任何可以 .print 的类型。提的需求也非常不明确。

    你希望模板自动推断存在可以构造的 Base 的子类 T ,还是希望 T 就是参数本身的类型,然后 obj.print 改写为 T1{obj}.print(),其中 T1 是 Base 的某个子类并且可以被 obj 所构造?

    另外,建议想清楚自己的问题,如果不是适合用模板解决的问题的话,会从一个小问题,变成一个报错非常长的问题。
    aglargilwangz
        3
    aglargilwangz  
       2023-08-11 16:27:19 +08:00   ❤️ 3
    ```cpp
    #include <iostream>

    struct Base {
    void print() {
    std::cout << "Base class\n";
    }
    };

    struct Str : Base {
    Str(const char* s) {}
    void print() {
    std::cout << "String class\n";
    }
    };

    struct Num : Base {
    Num(int v) {}
    void print() {
    std::cout << "Number class\n";
    }
    };

    template<typename T>
    struct Mapper;

    template<>
    struct Mapper<const char*> {
    using type = Str;
    };

    template<>
    struct Mapper<int> {
    using type = Num;
    };

    template<typename T>
    void log(T obj) {
    typename Mapper<T>::type mappedObj(obj);
    mappedObj.print();
    }

    int main() {
    log("hello");
    log(123);
    }
    ```
    hhjuteman
        4
    hhjuteman  
       2023-08-11 16:50:44 +08:00
    你给的代码编译不了,我用的楼上的。
    搞不懂的多用 godbolt
    https://godbolt.org/z/cMs4918PW
    ysc3839
        5
    ysc3839  
       2023-08-11 16:52:44 +08:00 via Android
    如果不想手动写明 log 的类型,想要自动判断是否能构造的话,可以用 std::is_constructible ,类似这样:
    template<typename T, typename std::enable_if<std::is_constructible<Str, T>{}, bool>::type = true>
    void log(T obj) {
    Str(obj).print();
    }
    这种方法仍然需要手动把所有继承的类型都写一遍,应该是无法避免的。
    另外这种写法有隐式转换的问题,比如传入 bool float 等类型也会匹配到 int 的。
    timethinker
        6
    timethinker  
       2023-08-11 17:07:25 +08:00
    如果希望模板根据特定的类型参数进行不同的生成策略,可以使用 Template specialization ,不过还是要看使用场景,有可能不需要模板,只需要重载函数就行了。
    MoYi123
        7
    MoYi123  
       2023-08-11 17:38:21 +08:00
    如果你的需求就是 print, 看看我写的这个 https://github.com/mmooyyii/mmooyyii/blob/master/codes/print.cpp

    template 和继承没多大关系. 感觉你需要再学一遍 template
    hankai17
        8
    hankai17  
       2023-08-11 17:50:46 +08:00
    一楼就挺好 面向编译期编程 直接暴力
    三楼也挺好 类型擦除
    geelaw
        9
    geelaw  
       2023-08-11 19:38:29 +08:00
    针对追加的具体问题

    struct Base { void print() { } };
    struct Str : Base { Str(char const *s) { } };
    struct Num : Base { Num(int v) { } };

    Base log_deduce(Base obj) { return obj; }
    Str log_deduce(Str obj) { return obj; }
    Str log_deduce(char const *s) { return s; }
    Num log_deduce(Num obj) { return obj; }
    Num log_deduce(int v) { return v; }

    template <typename... T>
    void log_impl(T... args)
    {
    /* fold expression from C++17 */
    ((void)(args.print()), ...);
    }

    template <typename... T>
    void log(T... args)
    {
    log_impl(log_deduce(args)...);
    }

    int main()
    {
    log("hello");
    log(123);
    log(123, "hello");
    }

    但我感觉楼主的提供的例子离真实用例很远。
    ysc3839
        10
    ysc3839  
       2023-08-11 20:05:55 +08:00 via Android
    “能直接指定模板参数的类型集合就好了,例如 Str | Num”
    理论上用可变参数模板+std::is_constructible 是可以实现的,不过写起来感觉会挺复杂的,不建议这么做。
    lovelylain
        11
    lovelylain  
       2023-08-11 20:17:01 +08:00
    看代码:
    ```c++
    #include <iostream>

    struct Base {
    template<typename T>
    explicit Base(T v) : type("Base") {}
    Base(const char* type) : type(type) {}
    void print() {
    std::cout << type << std::endl;
    }
    private:
    const char* type;
    };

    struct Str : Base {
    Str(const char* s):Base("Str") {}
    };

    struct Num : Base {
    Num(int v):Base("Num") {}
    };

    template <typename T>
    struct LogHelper {};

    template<>
    struct LogHelper<Base> {
    static void print(Base obj) { obj.print(); }
    };

    template<>
    struct LogHelper<const char*> {
    static void print(const char* s) { Str(s).print(); }
    };

    template<>
    struct LogHelper<int> {
    static void print(int v) { Num(v).print(); }
    };

    template<typename T>
    void log(T obj) {
    LogHelper<T>::print(obj);
    }

    int main() {
    log("hello");
    log(123);
    //log(1.23);
    log(Base(1.23));
    return 0;
    }
    ```
    leonshaw
        12
    leonshaw  
       2023-08-11 22:40:25 +08:00
    定义个 trait ,log 函数里面再构造 obj 呢?
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1067 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 19:16 · PVG 03:16 · LAX 11:16 · JFK 14:16
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.