模板参数作为函数未命名参数

Template argument as function unnamed argument

这个问题继续

这是我用来 return 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

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

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';

您当前所有的重载都采用对象。您可能会采用 type 或 object which hold 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;
};

(当我开始写答案时,问题没有答案,但当我正要 post 它时,我看到了@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 的主要模板可以删除(如上所述),或者实现给定类型没有映射字符串的自定义消息。

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

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

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

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

与其他答案中讨论的标签分发和专业化技术不同,这里是使用 's constexpr if.

的不同方法
  • 首先,我们使用特征(mem_type)

    从成员指针中找到成员的类型
  • 其次,我们编写一个内部辅助函数(即helper::class2str()),它使用编译时类型检查并丢弃错误分支(即if constexpr),这样我们正确地 return 将数据类型表示为 const char* 文字(因为我们可以使函数 constexpr)!

  • 最后,我们将有 main class2str(),它实际检查模板参数类型是否为成员指针,并使用 if constexpr 再次执行分支。如果模板类型是成员指针,我们使用特征 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)