构造函数无法从概念中推断模板参数

Constructor can't infer template argument from concept

为什么在这种情况下构造函数无法从概念中推断出模板参数?也许还有一种方法可以在序列化程序概念中不指定具体的模板类型。

#include <span>

template <class impl_t>
concept stream = requires(impl_t &impl) {
  { impl.write(std::span<const std::byte>{}) };
};

struct any_t {};
template <class impl_t, class stream_t>
concept serializer = requires(impl_t &impl, const stream_t &stream, any_t &any) {
  ::stream<stream_t>;
  { impl.serialize(any) } -> std::same_as<stream_t>;
};

class memory_stream {
public:
  void write(std::span<const std::byte> data) noexcept {
    
  }
};

template <stream stream_t>
class json_serializer {
public:
  template <typename T>
  stream_t serialize(const T &value) {
    return {};
  }
};

template <stream stream_t, serializer<stream_t> serializer_t>
class client {
public:
  explicit client(serializer_t &serializer) noexcept {
    
  }
};

int main() {
  json_serializer<memory_stream> s;
  client c(s);
}

Godbolt link

这不是 C++20 中推导的工作方式。

对于class模板参数推导(CTAD),clientclass模板需要推导两种类型(stream_tserializer_t)和您拥有的构造函数仅使用其中一种类型,因此该语言不知道另一种可能来自何处。

您可以通过为 json_serializer:

提供推导指南来帮助解决这个问题
template <stream S>
client(json_serializer<S>) -> client<S, json_serializer<S>>;

现在 client c(s); 可以工作了,因为您告诉它流类型在此上下文中的来源。如果您为序列化程序关联的 what 流添加一些关联类型,则这可以推广到任何类型的序列化程序。

猜测:

template <typename S>
using stream_for = decltype(std::declval<S&>().serialize(42));

template <typename Serializer>
client(Serializer) -> client<stream_for<Serializer>, Serializer>;

如果 serializer 有一个一元概念而不是二元概念,这会有所帮助。


请注意,在您的概念定义中,要求:

::stream<stream_t>;

不检查 stream_t 是否满足 stream 概念。它检查这是一个有效的表达式。无论是否满足该概念(falsetrue 一样有效)。

这需要是:

requires ::stream<stream_t>;

或者,甚至更好:

template <class Serializer, class Stream>
concept serializer_for =
    stream<Stream>
    && requires (Serializer& s, any_t a) {
           { s.serialize(a) } -> std::same_as<Stream>;
       };

您可以将 class 重写为

template <stream stream_t, template <typename> typename S> 
requires serializer<S<stream_t>,stream_t> 
class client{
   explicit client(S<stream_t> &serializer){}
}

或者您可以添加推演指南

template<typename Stream, template<typename> typename Serializer>
client(Serializer<Stream>) -> client<Stream,Serializer<Stream>>;

@Barry 也指出,您需要修改约束定义。 例如:

template <class T>
concept stream = requires(T &t) {
  { t.write(std::span<const std::byte>{}) };
};

struct any_t {};
template <class T, class S>
concept serializer = stream<S> && requires(T t, any_t &any) {
  { t.serialize(any) } -> std::same_as<S>;
};