C++: How do I compose multiple concepts?
Asked Answered
P

1

8

This title seems to receive a lot of criticism. However, I have found this notion to be difficult to search for. It is also relatively new in C++ land, where schools are still teaching C++11.

Motivation and Context to Avoid the XY-Problem

Right now, I am instrumenting out an embedded device project with concepts as part of the reverse engineering (and learning) process. The problem with device libraries and device APIs is that there are all sorts of programming no-no's that boil out of the literal device (or product line of devices) capabilities:

  • Hard coded settings
  • extensions
  • macros
  • specific bundles or subsets of device functionality
  • which leverage macros and helper classes to build structs (structs building structs)
  • no simplifying assumptions: massive error handling baggage for cross-platforming and device testing
  • etc.

The code winds up -- no matter how hard the devs tried, I know there is no better result possible -- being very verbose and complicated. In other words, it is functionally simple, but hard to grok.

Typically, I might form structs to wrap bundles and hide away hard-coded content, but now I am using concepts because they allow me to construct my interfacing namespace that can be changed or updated by me (a lone developer) much more efficiently in terms of knowledge. The less I have to know about the device to use the device, the better.

With concepts, I can also protect code I write which depends on my conceptualized content from change propagation by the device manufacturer and api providers.


In leveraging these concepts, I am trying to make early choices about how I structure my code.

In order to make a decision, I need to know whether it is possible for concepts to be written atomically, then composed into conceptual structures. For example:

// real code

// Concepts

template < typename ContextInfo>
concept ExtensibleDeviceContext = requires(ContextInfo & contextInfo, const char * name, 
                                           bool optional, void * devFeatures)
{
    { contextInfo.addDeviceExtension(name, optional, devFeatures)};
};

template< typename ContextInfo>
concept ExtensibleInstanceContext = requires(ContextInfo & contextInfo, const char * name, 
                                             bool optional)
{
  { contextInfo.addInstanceLayer(name, optional) };
  { contextInfo.addInstanceExtension(name, optional) };
};

// Some dummy functions

template<ExtensibleDeviceContext Cx>
void foo(Cx &context, const char *name, bool optional, void *devFeatures)
{
   context.addDeviceExtension(name, optional, devFeatures, version);
   context.addDeviceExtension(FOO_MACRO_1);
}

template<ExtensibleInstanceContext Cx>
void bar(Cx &context, const char *name, bool optional)
{
   context.addInstanceLayer(name, optional);
   context.addInstanceExtension(BAR_MACRO);
}

And what I am trying to do is, essentially, compose the concepts into a bundle of concepts within the template context "<...>":

// psuedo-code: what I am trying to achieve

template<typename PortInfo: {ExtensibleInstanceContext, ExtensibleDeviceContext}>
void foobar(
      PortInfo port, 
      const char *portName, 
      bool optional, 
      const char *devName, 
      bool devOptional, 
      void *devFeatures
    )
{
   foo(port, devName, devOptional, devFeatures);
   bar(port, portName, optional);
};

Is this sort of concept composition possible? If so, is it possible to do it in the template grammar, outlined above? If not, is there some "better" or "intended" way this should be done instead?

Pussyfoot answered 2/2, 2021 at 14:29 Comment(0)
G
5

You can constrain foobar with a requires clause, or name the conjunction of concepts you desire.

template<typename PortInfo>
requires ExtensibleInstanceContext<PortInfo> && ExtensibleDeviceContext<PortInfo>
void foobar(
      PortInfo port, 
      const char *portName, 
      bool optional, 
      const char *devName, 
      bool devOptional, 
      void *devFeatures
    )
{
   foo(port, devName, devOptional, devFeatures);
   bar(port, portName, optional);
};

Or

template <typename ContextInfo>
concept ExtensibleDeviceInstanceContext = ExtensibleInstanceContext<ContextInfo> && ExtensibleDeviceContext<ContextInfo>;

template <ExtensibleDeviceInstanceContext PortInfo>
void foobar(
      PortInfo port, 
      const char *portName, 
      bool optional, 
      const char *devName, 
      bool devOptional, 
      void *devFeatures
    )
{
   foo(port, devName, devOptional, devFeatures);
   bar(port, portName, optional);
};

You can use &&, || and ! with their usual boolean meanings to combine concepts.

Grummet answered 2/2, 2021 at 14:36 Comment(1)
outstanding -- concepts are way more expressive than expectedPussyfoot

© 2022 - 2024 — McMap. All rights reserved.