使用Concepts Lite为具有成员函数模板的类型指定概念

时间:2021-01-23 17:07:44

I'm trying to specify a concept to constrain a higher kinded type that has a member function template using Concepts Lite. However I am not able to find inside the technical specification or the tutorial a clause dealing with templated statements inside a concept.

我正在尝试使用Concepts Lite来指定一个概念来约束具有成员函数模板的更高级的kinded类型。但是,我无法在技术规范或教程中找到一个处理概念中模板化语句的子句。

How is this done?

这是怎么做到的?

Example: suppose I have the higher kinded type HKT with a member function template F:

示例:假设我有更高的kinded类型HKT和成员函数模板F:

template<class T>
struct HKT {
  template<class U> // this looks like e.g. rebind in std::allocators
  auto F(U) -> HKT<U>;
};

and that now I want to specify a concept for constraining these higher kinded types:

现在我想指定一个用于约束这些更高级的类型的概念:

template <template <class> class HKT, class T>
concept HKTWithTemplateMemberFunctionF {
  return requires(HKT<T> h) { // HKT<T> is a type, h is an object
    // HKT<T> needs to have a member function template that 
    // returns HTK<U> where the type U is to be deduced and
    // it can be any type (it is unconstrained)
    template<class U>  // is there a syntax for this?
    h.F(std::declval<U>()) -> HKT<U>; 
  }
}

Note that I could do something like:

请注意,我可以这样做:

template <template <class> class HKT, class T, class U>
concept HKTWithTemplateMemberFunctionF {
  return requires(HKT<T> h) {
      h.F(std::declval<U>()) -> HKT<U>;
  }
}

but this means that I need to know U at constraint site.

但这意味着我需要在约束站点知道U.

I don't really care if substitution for a given U fails or not although I can see why this could be a problem: e.g. apply a constraint to be sure your function doesn't fail and then fails cause the constraint was satisfied but at instantiation time substitution failed in the member function template (would it help if the member function template was constrained?).

我真的不在乎替换给定的U是否失败,尽管我可以理解为什么这可能是一个问题:例如应用约束以确保您的函数不会失败然后失败导致约束被满足但在实例化时成员函数模板中的替换失败(如果成员函数模板受约束会有帮助吗?)。

2 个解决方案

#1


2  

Let's think about the requirements you want from your comment:

让我们从您的评论中考虑您想要的要求:

// HKT<T> needs to have a member function template that 
// returns HTK<U> where the type U is to be deduced and
// it can be any type (it is unconstrained)

While Concepts requires us to base our constraints around concrete types, we can be smart in our selection of which concrete types we use. What do you mean by U is any type. Really any type at all, whatsoever? Think about the smallest possible set of constraints you have on U and let's build a type that satisfies them. This is known as an archetype of U.

虽然Concepts要求我们根据具体类型建立约束,但我们可以选择使用哪种具体类型。你是什​​么意思U是任何类型。真的是任何类型,无论如何?考虑一下你在U上可能的最小约束条件,让我们构建一个满足它们的类型。这被称为U的原型。

My goto first thought for "any type" would actually be a semiregular type. A type that is default constructible, copyable, and assignable. All the normal goodies:

我的转到首先想到的“任何类型”实际上都是半规则类型。一种默认可构造,可复制和可分配的类型。所有正常的好东西:

namespace archetypes {
    // private, only used for concept definitions, never in real code
    struct Semiregular { };
}

archetypes::Semiregular is a concrete type, so we can use it to build a concept:

archetypes :: Semiregular是一种具体类型,因此我们可以使用它来构建一个概念:

template <template <class> class HKT, class T>
concept bool HKTWithTemplateMemberFunctionF = 
  requires(HKT<T> h, archetypes::Semiregular r) {
    {h.F(r)} -> HKT<archetypes::Semiregular>
  };

archetypes::Semiregular is a private type. It should not be known to HKT, and so if h.F(r) is well-formed and returns a type convertible to HKT<archetypes::Semiregular>, it's almost certainly a member function template.

archetypes :: Semiregular是一种私人类型。 HKT不应该知道,所以如果h.F(r)格式正确并返回一个可转换为HKT 的类型,它几乎可以肯定是一个成员函数模板。

The question then is, is this a good archetype? Do we need U to be semiregular, or would irregular types work too? The fewer operations that you need, the fewer should be present in your archetype. Maybe all you need is that U is movable:

那么问题是,这是一个好的原型吗?我们需要U是半规则的,还是不规则的类型也可以工作?您需要的操作越少,原型中应该出现的操作越少。也许你需要的只是U是可移动的:

namespace archetypes {
    // private, only used for concept definitions, never in real code
    struct Semiregular { };

    struct Moveable {
        Moveable() = delete;
        Moveable(Moveable&& ) noexcept(false);
        Moveable(Moveable const& ) = delete;
        ~Moveable() = default;

        Moveable& operator=(Moveable const& ) = delete;
        Moveable& operator=(Moveable&& ) noexcept(false);
    };
}

template <template <class> class HKT, class T>
concept bool HKTWithTemplateMemberFunctionF =
  requires(HKT<T> h, archetypes::Moveable m) {
    { h.F(m) } -> HKT<archetypes::Moveable>
  };

We're testing the same idea - invoking F() with a type that isn't well-known and excepting the return type to reflect that, hence requiring it to be a function template. But now we're giving less functionality to the type. If F() works on any, it'll work on archetypes::Moveable.

我们正在测试相同的想法 - 使用一种不为人所熟知的类型调用F(),并且除了返回类型以反映它,因此要求它是一个函数模板。但是现在我们给这种类型提供了更少的功能。如果F()适用于任何,它将适用于archetypes :: Moveable。

Keep iterating on this idea until you've really pared down the required functionality to the bare minimum. Maybe you don't even need the archetype to be destructible? Writing archetypes is hard, but in cases like this, it's important to get right.

继续迭代这个想法,直到你真正削减所需的功能到最低限度。也许你甚至不需要原型可以破坏?写作原型很难,但在这种情况下,正确的做法很重要。

#2


7  

Long story short, right now you (I?) have to provide a specific U:

长话短说,现在你(我?)必须提供一个特定的U:

template <template <class> class HKT, class T, class U = T>
concept HKTWithTemplateMemberFunctionF {
  return requires(HKT<T> h) { // HKT<T> is a type, h is an object
    h.F(std::declval<U>()) -> HKT<U>; 
  }
}

since the compiler cannot prove for all types U that might ever exist that the member-function template will work, that is, the following is hopeless:

因为编译器无法证明成员函数模板可能存在的所有类型U,也就是说,以下是没有希望的:

template <template <class> class HKT, class T>
concept HKTWithTemplateMemberFunctionF {
  return requires(HKT<T> h) {
    template<class U>  // for all those Us that haven't been written yet...
    h.F(std::declval<U>()) -> HKT<U>; 
  }
}

In an hypothetical designed-in-5-min concepts-lite implementation where we are able to constrain U just a little-bit:

在一个假设的5分钟概念设计实现中,我们能够稍微约束一下U:

template <template <class> class HKT, class T, 
          InputIterator U = InputIterator()  /* imaginary syntax */ >
concept HKTWithTemplateMemberFunctionF {
  return requires(HKT<T> h) {
      h.F(std::declval<U>()) -> HKT<U>; // Is InputIterator enough to instantiate F?
  }
}

the compiler would only need to check if a model of InputIterator is enough to instantiate h.F, which is possible even if h.F is not constrained! Additionally providing U just checks that it models InputIterator, no need to even try to check h.F for U since InputIterator is enough. This could be used to optimize compile-time performance and...

编译器只需要检查InputIterator的模型是否足以实例化h.F,即使h.F没有约束也是如此!另外提供U只是检查它是否为InputIterator建模,甚至不需要尝试检查U.F为U,因为InputIterator足够了。这可用于优化编译时性能和......

...would probably interact in surprising ways with SFINAE, since AFAIK you can have a concept-overloaded function (e.g. for InputIterator) that accepts all input iterators except that one (SFINAE! why would anyone do that?!), and thus could pass concept-checking but blow at instantiation time... sad.

...可能会以令人惊讶的方式与SFINAE交互,因为AFAIK你可以拥有一个概念重载函数(例如对于InputIterator),它接受除了那个之外的所有输入迭代器(SFINAE!为什么会有人这样做?!),因此可以通过概念检查,但在实例化时间吹...悲伤。

#1


2  

Let's think about the requirements you want from your comment:

让我们从您的评论中考虑您想要的要求:

// HKT<T> needs to have a member function template that 
// returns HTK<U> where the type U is to be deduced and
// it can be any type (it is unconstrained)

While Concepts requires us to base our constraints around concrete types, we can be smart in our selection of which concrete types we use. What do you mean by U is any type. Really any type at all, whatsoever? Think about the smallest possible set of constraints you have on U and let's build a type that satisfies them. This is known as an archetype of U.

虽然Concepts要求我们根据具体类型建立约束,但我们可以选择使用哪种具体类型。你是什​​么意思U是任何类型。真的是任何类型,无论如何?考虑一下你在U上可能的最小约束条件,让我们构建一个满足它们的类型。这被称为U的原型。

My goto first thought for "any type" would actually be a semiregular type. A type that is default constructible, copyable, and assignable. All the normal goodies:

我的转到首先想到的“任何类型”实际上都是半规则类型。一种默认可构造,可复制和可分配的类型。所有正常的好东西:

namespace archetypes {
    // private, only used for concept definitions, never in real code
    struct Semiregular { };
}

archetypes::Semiregular is a concrete type, so we can use it to build a concept:

archetypes :: Semiregular是一种具体类型,因此我们可以使用它来构建一个概念:

template <template <class> class HKT, class T>
concept bool HKTWithTemplateMemberFunctionF = 
  requires(HKT<T> h, archetypes::Semiregular r) {
    {h.F(r)} -> HKT<archetypes::Semiregular>
  };

archetypes::Semiregular is a private type. It should not be known to HKT, and so if h.F(r) is well-formed and returns a type convertible to HKT<archetypes::Semiregular>, it's almost certainly a member function template.

archetypes :: Semiregular是一种私人类型。 HKT不应该知道,所以如果h.F(r)格式正确并返回一个可转换为HKT 的类型,它几乎可以肯定是一个成员函数模板。

The question then is, is this a good archetype? Do we need U to be semiregular, or would irregular types work too? The fewer operations that you need, the fewer should be present in your archetype. Maybe all you need is that U is movable:

那么问题是,这是一个好的原型吗?我们需要U是半规则的,还是不规则的类型也可以工作?您需要的操作越少,原型中应该出现的操作越少。也许你需要的只是U是可移动的:

namespace archetypes {
    // private, only used for concept definitions, never in real code
    struct Semiregular { };

    struct Moveable {
        Moveable() = delete;
        Moveable(Moveable&& ) noexcept(false);
        Moveable(Moveable const& ) = delete;
        ~Moveable() = default;

        Moveable& operator=(Moveable const& ) = delete;
        Moveable& operator=(Moveable&& ) noexcept(false);
    };
}

template <template <class> class HKT, class T>
concept bool HKTWithTemplateMemberFunctionF =
  requires(HKT<T> h, archetypes::Moveable m) {
    { h.F(m) } -> HKT<archetypes::Moveable>
  };

We're testing the same idea - invoking F() with a type that isn't well-known and excepting the return type to reflect that, hence requiring it to be a function template. But now we're giving less functionality to the type. If F() works on any, it'll work on archetypes::Moveable.

我们正在测试相同的想法 - 使用一种不为人所熟知的类型调用F(),并且除了返回类型以反映它,因此要求它是一个函数模板。但是现在我们给这种类型提供了更少的功能。如果F()适用于任何,它将适用于archetypes :: Moveable。

Keep iterating on this idea until you've really pared down the required functionality to the bare minimum. Maybe you don't even need the archetype to be destructible? Writing archetypes is hard, but in cases like this, it's important to get right.

继续迭代这个想法,直到你真正削减所需的功能到最低限度。也许你甚至不需要原型可以破坏?写作原型很难,但在这种情况下,正确的做法很重要。

#2


7  

Long story short, right now you (I?) have to provide a specific U:

长话短说,现在你(我?)必须提供一个特定的U:

template <template <class> class HKT, class T, class U = T>
concept HKTWithTemplateMemberFunctionF {
  return requires(HKT<T> h) { // HKT<T> is a type, h is an object
    h.F(std::declval<U>()) -> HKT<U>; 
  }
}

since the compiler cannot prove for all types U that might ever exist that the member-function template will work, that is, the following is hopeless:

因为编译器无法证明成员函数模板可能存在的所有类型U,也就是说,以下是没有希望的:

template <template <class> class HKT, class T>
concept HKTWithTemplateMemberFunctionF {
  return requires(HKT<T> h) {
    template<class U>  // for all those Us that haven't been written yet...
    h.F(std::declval<U>()) -> HKT<U>; 
  }
}

In an hypothetical designed-in-5-min concepts-lite implementation where we are able to constrain U just a little-bit:

在一个假设的5分钟概念设计实现中,我们能够稍微约束一下U:

template <template <class> class HKT, class T, 
          InputIterator U = InputIterator()  /* imaginary syntax */ >
concept HKTWithTemplateMemberFunctionF {
  return requires(HKT<T> h) {
      h.F(std::declval<U>()) -> HKT<U>; // Is InputIterator enough to instantiate F?
  }
}

the compiler would only need to check if a model of InputIterator is enough to instantiate h.F, which is possible even if h.F is not constrained! Additionally providing U just checks that it models InputIterator, no need to even try to check h.F for U since InputIterator is enough. This could be used to optimize compile-time performance and...

编译器只需要检查InputIterator的模型是否足以实例化h.F,即使h.F没有约束也是如此!另外提供U只是检查它是否为InputIterator建模,甚至不需要尝试检查U.F为U,因为InputIterator足够了。这可用于优化编译时性能和......

...would probably interact in surprising ways with SFINAE, since AFAIK you can have a concept-overloaded function (e.g. for InputIterator) that accepts all input iterators except that one (SFINAE! why would anyone do that?!), and thus could pass concept-checking but blow at instantiation time... sad.

...可能会以令人惊讶的方式与SFINAE交互,因为AFAIK你可以拥有一个概念重载函数(例如对于InputIterator),它接受除了那个之外的所有输入迭代器(SFINAE!为什么会有人这样做?!),因此可以通过概念检查,但在实例化时间吹...悲伤。