如何根据一些条件推断模板参数的类型并返回关于该类型的信息。

huangapple go评论56阅读模式
英文:

How to deduce the type of template argument based on some conditions and return information about that type

问题

假设我有一个类 ```C```,它包含一个 ```std::variant<A, B>```。```C``` 的构造函数必须是一个模板构造函数,可以接受任何类型,并根据该类型以不同的方式初始化 variant

这是一个简化的概述:
```cpp
struct A {
    // ...
};

struct B {
    // ...
};

class C {
    public:
        template <typename T>
        C(T arg) {
            if constexpr (std::same_v<B, T>) {
                var = B{arg};
            }
            else if constexpr (std::is_constructible<A, T>) {
                var = A{arg};
            }
        }

    private:
        std::variant<A, B> var;
};

我正在寻找一种使用一些模板元编程技巧来合并这些 if constexpr 语句的方法,以便我可以像这样重写构造函数(希望避免额外初始化 variant):

template<T>
struct DeduceAOrB {
  // ..... somehow check the conditions here 
};

template <typename T>
C(T arg)
    : var(DeduceAOrB<T>::type{arg})
{}

重要注意事项是 variant 可能在将来需要扩展,因此解决方案必须适用于任意多个类型。

英文:

Suppose I have a class C that has holds a std::variant<A, B>. The constructor of C must be a templated constructor that can accept any type, and based on that type it must initialize the variant in different ways.

Here is a simplified overview:

struct A {
    // ...
};

struct B {
    // ...
};

class C {
    public:
        template <typename T>
        C(T arg) {
            if constexpr (std::same_v<B, T>) {
                var = B{arg};
            }
            else if constexpr (std::is_constructible<A, T>) {
                var = A{arg};
            }
        }

    private:
        std::variant<A, B> var;
};

I am looking for a way to squash these if constexpr statements by using some template metaprogramming magic, so I may rewrite the constructor like this (hoping to avoid the extra initialization of the variant):

template<T>
struct DeduceAOrB {
  // ..... somehow check the conditions here 
};

template <typename T>
        C(T arg)
            : var(DeduceAOrB<T>::type{arg})
        {}

Important note is that the variant may need to be expanded in the future, so the solution must be viable for arbitrarily many types

答案1

得分: 3

以下是翻译好的代码部分:

class C {
    private:
        // 包含条件并根据结果返回的辅助函数
        template<typename T>
        static constexpr auto helperFunc(T arg)
        {
            if constexpr (std::is_same_v<B, T>) {
                        return B{arg};
            }
            else if constexpr (std::is_constructible_v<A, T>) {
                        return A{arg};
            } else {
                        return A{};
            }
        }
    public:
        template <typename T>
//----------------VVV------------------> 在初始化列表中使用 helperFunc
        C(T arg): var(helperFunc(arg)){
            
        }

    private:
        std::variant<A, B> var;
};
英文:

One way is to create a helper function containing the conditions and use it in initializer list as shown below:

class C {
    private:
        //helper function checking for different conditions and returning based on result
        template&lt;typename T&gt;
        static constexpr auto helperFunc(T arg)
        {
            if constexpr (std::is_same_v&lt;B, T&gt;) {
                        return B{arg};
            }
            else if constexpr (std::is_constructible_v&lt;A, T&gt;) {
                        return A{arg};
            } else {
                        return A{};
            }
        }
    public:
        template &lt;typename T&gt;
//----------------VVV------------------&gt;use helperFunc initializer list
        C(T arg): var(helperFunc(arg)){
            
            
        }

    private:
        std::variant&lt;A, B&gt; var;
};

答案2

得分: 1

std::conditional 是为了处理这种情况而创建的。它的 type 会根据提供的条件选择两种类型中的一种:

std::conditional_t<boolean,
                   true-type,
                   false-type>

当然,你可以嵌套使用它们,根据需要可以有多个条件,就像这种情况:

std::conditional_t<boolean1,
                   true-type1,
std::conditional_t<boolean2,
                   true-type2,
                   false-type>>

因此,DeduceAOrB 可以这样定义:

template<class T>
using DeduceAOrB = 
std::conditional_t<std::is_same_v<B, std::remove_cv_t<std::remove_reference_t<T>>>,
    B,
std::conditional_t<std::is_constructible_v<A, T>,
    A,
    defaulter<A>>>;

你可以将当前使用 if constexpr 的构造函数重写如下:

template <class T>
C(T&& arg) : var{DeduceAOrB<T>{std::forward<T>(arg)}} {}

它使用了与你构造函数中使用的逻辑完全相同的逻辑:

  • 如果 std::is_same_v<B, T> 为真 - 从 arg 构造一个 B,否则
  • 如果 std::is_constructible_v<A, T> 为真 - 从 arg 构造一个 A,否则
  • 默认构造一个 A

defaulter 类模板用于创建一个可以从任何参数构造并可以隐式转换为 T 的类型,这用于在两个条件都失败的情况下默认构造一个 A

template <class T>
struct defaulter {
    template <class... Args> defaulter(Args&&...) {}
    operator T() const { return T{}; }
};

完整示例

英文:

> I am looking for a way to squash these if constexpr statements by using some template metaprogramming magic

std::conditional was created for cases like this. It's type will be one of two supplied types depending on the supplied condition:

std::conditional_t&lt;boolean,
                   true-type,
                   false-type&gt;

and you can of course nest them with as many conditions as you'd like, which will be needed in this case:

std::conditional_t&lt;boolean1,
                   true-type1,
std::conditional_t&lt;boolean2,
                   true-type2,
                   false-type&gt;&gt;

DeduceAOrB could therefore be defined as such:

template&lt;class T&gt;
using DeduceAOrB = 
std::conditional_t&lt;std::is_same_v&lt;B, std::remove_cv_t&lt;std::remove_reference_t&lt;T&gt;&gt;&gt;,
    B,
std::conditional_t&lt;std::is_constructible_v&lt;A, T&gt;,
    A,
    defaulter&lt;A&gt;&gt;&gt;;

Your current constructor using if constexpr could then be rewritten like so:

template &lt;class T&gt;
C(T&amp;&amp; arg) : var{DeduceAOrB&lt;T&gt;{std::forward&lt;T&gt;(arg)}} {}

It uses the exact same logic as you use in your constructor:

  • if std::is_same_v&lt;B, T&gt; - construct a B from arg, else
  • if std::is_constructible_v&lt;A, T&gt; - construct an A from arg, else
  • default construct an A

The defaulter class template is used to create a type that can be constructed from any arguments and is implicitly convertible to T, which is used to default construct an A in case both conditions fails.

template &lt;class T&gt;
struct defaulter {
    template &lt;class... Args&gt; defaulter(Args&amp;&amp;...) {}
    operator T() const { return T{}; }
};

Full demo

答案3

得分: 0

如果这只是一次性的操作,你可以像下面展示的那样使用 lambda。否则,你可以创建一个辅助函数。

class C {
public:
    template <typename T>
    //--------------v-------------------->使用构造函数初始化列表
    C(T arg): var([&](){
        if constexpr (std::is_same_v<B, T>) {
            return B{arg};
        }
        else if constexpr (std::is_constructible_v<A, T>) {
            return A{arg};
        }}()) {
        
    }

private:
    std::variant<A, B> var;
};
英文:

If this is a one time thing, you can use lambda as shown below. Otherwise you can create a helper function.

class C {
    public:
        template &lt;typename T&gt;
//--------------v--------------------&gt;use constructor initializer list
        C(T arg): var([&amp;](){
            if constexpr (std::is_same_v&lt;B, T&gt;) {
                return B{arg};
            }
            else if constexpr (std::is_constructible_v&lt;A, T&gt;) {
                return A{arg};
            }}()) {
            
        }

    private:
        std::variant&lt;A, B&gt; var;
};

答案4

得分: 0

这是作为类型包装器的std::type_identity的一个候选项:

template<class T>
struct DeduceAOrB : decltype([]
    {
        if constexpr (std::same_v<B, T>) {
            return std::type_identity<B>();
        } else if constexpr (std::is_constructible<A, T>) {
            return std::type_identity<A>();
        }
    }()) {
    // 'type' alias inherited from 'std::type_identity' base class
};

如果你愿意,甚至可以内联这个决策:

class C {
    public:
        template <typename T>
        C(T arg) : var(typename decltype([&]
        {
            if constexpr (std::same_v<B, T>) {
                return std::type_identity<B>();
            } else if constexpr (std::is_constructible<A, T>) {
                return std::type_identity<A>();
            }
        }())::type{arg}) {
        }

    private:
        std::variant<A, B> var;
};
英文:

This is a candidate for std::type_identity as a wrapper for types:

template&lt;class T&gt;
struct DeduceAOrB : decltype([]
    {
        if constexpr (std::same_v&lt;B, T&gt;) {
            return std::type_identity&lt;B&gt;();
        } else if constexpr (std::is_constructible&lt;A, T&gt;) {
            return std::type_identity&lt;A&gt;();
        }
    }()) {
    // &#39;type&#39; alias inherited from &#39;std::type_identity&#39; base class
};

You can even inline this decision, should you want to:

class C {
    public:
        template &lt;typename T&gt;
        C(T arg) : var(typename decltype([&amp;]
        {
            if constexpr (std::same_v&lt;B, T&gt;) {
                return std::type_identity&lt;B&gt;();
            } else if constexpr (std::is_constructible&lt;A, T&gt;) {
                return std::type_identity&lt;A&gt;();
            }
        }())::type{arg}) {
        }

    private:
        std::variant&lt;A, B&gt; var;
};

huangapple
  • 本文由 发表于 2023年6月2日 00:27:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/76383951.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定