2

Hi I am new to C++ and I am trying to create a class hierarchy where each class represents a node in a schema document, think json-schema. Have a look at the representation of e.g. a string. A string can have three optional constraints

  • min_length
  • max_length
  • pattern

Furthermore a string is a type so it would make sense to have a base class representing a type from which all types (boolean, number, ...) inherit. Now, one way to achieve this is by writing something like

struct Type { const std::string m_name; Type(const std::string& name) : m_name{name} {} virtual X Serialize() const { //... } }; struct String : Type { const int m_max_len; const int m_min_len; const std::string m_pattern; String(int min_len, int max_len, const std::string& pattern) : Type("string") , m_min_len(min_len) , m_max_len(max_len) , m_pattern(pattern) {} X Serialize() const override { // If min_length was not set then it should be omitted from the serialized output. } }; 

This String implementation would not make the constraints optional. What to do?

Options:

  • One could employ a strategy where the default constructor parameters where set to some "illegal" value like INT_MIN (which would work in this case because the length can not be negative) but that would not work in the general case. It could very well be that all possible integers a legal values, the same goes for the pattern parameter.
  • You do not want to have different constructors for every possible permutation of optional parameters. In this case there are three optional values which would yield 2^3 different constructors. Also it would not be possible for the compiler to distinguish between the constructors String(int min_length) and String(int max_length).
  • It would be possible to do something like

    String(int* min_length = nullptr, int* max_length = nullptr, const std::string* nullptr)

    but then you would have to use new/delete or give lvalues for the set parameters.
  • Finally each member could be a std::unique_ptr.

    String(std::unique_ptr<int> min_value nullptr, std::unique_ptr<int> max_value = nullptr, std::unique_ptr<const std::string> pattern = nullptr)

    but then you would end up with quite complicated calls when creating an instance of String. Also if implementing a container of Types which may have optional parameters of its own things quickly get out of hand.

Finally, the code must be compatible with C++14.

11
  • 3
    I'm not sure what your question actually is. Commented Nov 20, 2020 at 6:33
  • 1
    It will be a lot easier to not use polymorphism here, I think Commented Nov 20, 2020 at 6:34
  • 1
    "because the length can not be negative" -- when a value cannot be negative, you should be using unsigned integers, not plain int. Commented Nov 20, 2020 at 6:42
  • 1
    I think you should create a class for every parameter, So your call looks like String(MinLength::default(),MaxLength(42),string). Also you might find inspiration in this blog or this. Commented Nov 20, 2020 at 6:50
  • 1
    Why is Type relevant to your question? When I skip the parts of your question that refer to your type hierarchy, it makes as much sense as when I read the whole thing. It seems like this question should be focused on your class with optional parameters, to the exclusion of polymorphism. The one relevant detail I see hidden in the polymorphism discussion is a mention that you need to distinguish between "parameter not given" and "given parameter imposes no restrictions" (an example of the latter would be setting the min length to 0). You might want to mention that detail in the text. Commented Nov 20, 2020 at 6:51

4 Answers 4

3

You can simply use std::optional:

String(const std::optional<int> &min_len, const std::optional<int> &max_len, const std::optional<std::string> &pattern); Type *type = new String(5, {}, std::nullptr); // last 2 parameters are omitted. 

For C++14 you can use similar constructs that exist in other open source libraries, (e.g. boost::optional, folly::Optional).

Sign up to request clarification or add additional context in comments.

2 Comments

The OP said "Must be compatible with C++14", and std::optional is C++17.
I don't think it's any good to pass std::optional by const reference. A user will likely not provide arguments of type std::optional, and temporaries will be created. Which is effectively the same as passing by value. However, you likely want to pass the string argument by const reference (not an optional built around its copy). In that case, you need something as std::optional<std::reference_wrapper<const std::string>> pattern. A user can pass std::nullopt then.
2

I lack reputation points to comment on @Kostas

To be compatible with C++14 you can try experimental namespace which has optional (if it is available/works on your compiler)

#include <experimental/optional> 

then you can use

String(std::experimental::optional<int> &min_len,.....) min_len.value_or(-1) 

1 Comment

I like this idea (upvote), but it doesn't seem to be aailable on Visual Studio 2019 with C++ 2014 :-( I hope that it wil help others, though
1

You can write your own class that can contain value or not if you can't use std::optional. It is not like lot of code. Can make its interface like std::optioal has or can make something different, what matters is data:

class OptInt { bool set_; int value_; public: OptInt() : set_(false) , value_(0) {} OptInt(int v) : set_(true), value_(v) {} // can add other ways how to create it bool isSet() const {return set_;} int get() const {if (!set_) throw 666; return value_;} // can add other operations you want to it. }; 

Then you can use such default-constructed OptInt as your default argument and so it will be not set, but if caller provides int argument then it will be set.

4 Comments

Minimal version does create value even if not set/provide, improved version (st std one) does not.
@Jarod42 people who say "I'm new to C++" should perhaps not mess with placement news that it takes to gain a tiny bit of performance. That can be added later if there is need for it.
I didn't ask to provide better (and more complicated) implementation, just inform that it can be improved if needed.(OP might have types non-default constructible for example).
This alludes to the answer given by @generic_opto_guy in the original post. I think I'm going to try this approach. Thanks!
-1

Have you tried to use an std::optional (since C++17)? I know you mentioned the need to use C++14 compatible code, but there is a boost::optional available.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.