c++20 概念:如何使用可能存在或不存在的类型?

c++20 concepts: How can I use a type that may or may not exist?

我已经开始了一个项目,该项目大量使用 c++20 概念作为学习一些新的 c++20 功能的方式。作为它的一部分,我有一个函数模板,它接受一个参数并对其进行操作。我希望能够灵活地将类型传递给此函数,以指定它们所操作的另一种类型,但如果该规范不存在,则默认为其他类型。

例如,这里有一个类型指定了它所操作的类型:

struct has_typedef_t
{
    typedef int my_type; //operates on this type
    void use_data(const my_type& data) const
    {
         //do something else
    }
};

还有一个没有指定类型,但在别处指定的默认类型上运行:

typedef std::string default_type;
struct has_no_typedef_t
{
    void use_data(const default_type& data) const
    {
        //do something
    }
};

我有一个简单的概念可以告诉我是否有任何给定类型具有此规范:

template <class T> concept has_type = requires(T t) {typename T::my_type;};

函数可能如下所示:

template <class T> void my_function(const T& t)
{
    //Here I want a default-constructed value of the default
    //type if the argument doesn't have a typedef
    typename std::conditional<has_type<T>, typename T::my_type, default_type>::type input_data;
    t.use_data(input_data);
}

这里的问题是第二个模板参数对于任何没有指定类型的东西都是无效的,例如has_no_typedef_t。示例程序:

int main(int argc, char** argv)
{
    has_no_typedef_t s1;
    has_typedef_t    s2;
    
    // my_function(s1); // doesn't compile:
    //'has_no_typedef_t has no type named 'my_type'
    
    my_function(s2); //compiles
    return 0;
}

我正在寻找的是以下行的替代品:

typename std::conditional<has_type<T>, typename T::my_type, default_type>::type input_data;

因为这是导致问题的原因。我知道我可以使用重载函数使用上面的概念并结合 decltype 和 declval 来使用一些技巧,但我正在寻找一些干净的东西并且无法想出任何东西。我怎样才能干净地实现这种行为?

下面是完整图片的完整代码(c++20):

#include <iostream>
#include <type_traits>
#include <concepts>
#include <string>

template <class T> concept has_type = requires(T t) {typename T::my_type;};

struct has_typedef_t
{
    typedef int my_type;
    void use_data(const my_type& data) const
    {
        //do something
    }
};

typedef std::string default_type;
struct has_no_typedef_t
{
    void use_data(const default_type& data) const
    {
        //do something else
    }
};



template <class T> void my_function(const T& t)
{
    //Here I want a default-constructed value of the default
    //type if the argument doesn't have a typedef
    typename std::conditional<has_type<T>, typename T::my_type, default_type>::type input_data;
    t.use_data(input_data);
}

int main(int argc, char** argv)
{
    has_no_typedef_t s1;
    has_typedef_t    s2;
    
    // my_function(s1); // doesn't compile:
    //'has_no_typedef_t has no type named 'my_type'

    my_function(s2); //compiles
    return 0;
}

自 C++98 以来已有解决方案:a traits class。概念只是让它更容易实现:

template<typename T>
struct traits
{
  using type = default_type;
};

template<has_type T>
struct traits<T>
{
  using type = T::my_type;
};

如果没有概念,您需要使用 SFINAE 根据类型是否具有特征来 on/off 特化。

您可以使用 lambda 结合 if constexpr 来确定 return 的类型。这里的type_identity是为了解决type可能不是default_initializable.

的问题
typename decltype([]{
  if constexpr (requires { typename T::my_type; }) 
    return std::type_identity<typename T::my_type>{};
  else
    return std::type_identity<default_type>{};
}())::type input_data;

t.use_data(input_data);

Demo