6

In my project there are a lot of strings with different meanings at the same scope, like:

std::string function_name = "name";
std::string hash = "0x123456";
std::string flag = "--configure";

I want to distinguish different strings by their meaning, to use with function overloads:

void Process(const std::string& string_type1);
void Process(const std::string& string_type2);

Obviously, I have to use different types:

void Process(const StringType1& string);
void Process(const StringType2& string);

But how to implement those types in an elegant manner? All I can come with is this:

class StringType1 {
  std::string str_;
 public:
  explicit StringType1(const std::string& str) : str_(str) {}
  std::string& toString() { return str_; }
};

// Same thing with StringType2, etc.

Can you advise more convenient way?


There is no point in renaming functions since the main goal is to not mistakenly pass one string type instead of another:

void ProcessType1(const std::string str);
void ProcessType2(const std::string str);

std::string str1, str2, str3;

// What should I pass where?..
abyss.7
  • 13,882
  • 11
  • 56
  • 100
  • _"distinguish different strings by their meaning"_ Could you elaborate on _their meaning_ please? Smells a bit of a XY problem for me. – πάντα ῥεῖ Nov 01 '14 at 08:45
  • Yeah, it's clearly a XY problem. It's overkill to have own classes encapsulating the data in this case. Have a look in strategy patterns. – πάντα ῥεῖ Nov 01 '14 at 08:50
  • @πάνταῥεῖ just to have a look - it is not very helpful. May be you can expand your suggested pattern usage to an answer? – abyss.7 Nov 01 '14 at 08:57
  • just `ProcessType1` and `ProcessType2` look much much better. Readable and easy code is good code, not short one. – ikh Nov 01 '14 at 09:00
  • Why is a hash which is clearly a number, represented as a string? – rubenvb Nov 01 '14 at 09:48
  • Actually, I like your style. More type safety is a worthwhile goal. Often, it will even be possible to remove your `toString` function completely and instead provide only those few functions of the convoluted `std::string` interface which you actually need. Such wrappers can be the first step to a much stricter encapsulation where clients of your classes don't know `std::string` is used internally. – Christian Hackl Nov 01 '14 at 11:39
  • Think about this: while you are trying to protect a programmer from himself by creating these new strings, you may end up making his life more confusing as he struggles to figure out why someone would wrap std::string – Nicolas Holthaus Nov 01 '14 at 12:06
  • Related: http://c2.com/cgi/wiki?StringlyTyped – Casey Nov 01 '14 at 13:50

5 Answers5

6

You probably want a template with a tag parameter:

template<class Tag>
struct MyString
{
    std::string data;
};

struct FunctionName;
MyString<FunctionName> function_name;
o11c
  • 15,265
  • 4
  • 50
  • 75
  • I don't think it's good to use function overload in this case, but +1 for tag - – ikh Nov 01 '14 at 09:15
2

Simple approach:

struct flag {
    string value;
};
struct name {
    string value;
};

You could improve this with implicit conversions to strings or other memberfunctions, too.

Ulrich Eckhardt
  • 16,572
  • 3
  • 28
  • 55
2

You could use a similar technique as I applied to std::vector in this answer.

Here's how it would look like for std::string:

#include <iostream>
#include <string>

template<typename Tag, class T>
struct allocator_wrapper : T
{ using T::T; };

template< typename Tag,
          typename CharT = char,
          typename Traits = std::char_traits<CharT>,
          typename Allocator = std::allocator<CharT> >
using MyString = std::basic_string<CharT,Traits,allocator_wrapper<Tag, Allocator>>;

class HashTag;
class FlagsTag;

using Hash = MyString<HashTag>;
using Flags = MyString<FlagsTag>;

void foo( Hash ) {}

int main()
{
    Hash hash( "12345" );
    Flags flags( "--foo" );

    foo( hash ); // that's fine

    // you can already use them like strings in *some* contexts
    std::cout << hash << " - " << flags << std::endl;
    std::cout << ( hash == "12345" ) << std::endl;

    // but they are not compatible types:
    // won't compile:
    // foo( flags );
    // std::cout << ( hash == flags ) << std::endl;
}
Community
  • 1
  • 1
Daniel Frey
  • 55,810
  • 13
  • 122
  • 180
1

The design you are aiming for is inheritance, as in the other answer here(*). But you should not inherit from std::string. You can find many discussions about it, for example: Inheriting and overriding functions of a std::string?.

It leaves you with your first idea, actually implementing the concept of composition.

(*) I would have commented in that answer instead of opening a new answer, but I can't comment yet.

Community
  • 1
  • 1
Oren Kishon
  • 509
  • 6
  • 8
0

This all sounds a bit bass ackwards. You want to have the same name for multiple functions that do different things to different objects. That's rather strange, because the typical approach is to have the same name for functions that do different things.

Not passing "the wrong things" is largely up to the programmer, you should remember what you are doing, and to what. And to test your code as you write it, and have tests that you run regularly to avoid regressions.

The other approach is to have a set of classes that hold your data, and have a common interface, such as this:

class OptionBase
{
public:
   OptionBase(const std::string &s) : str(s) {}
   virtual void Process() = 0;
   virtual std::string Value() { return str; }
   virtual ~OptionBase() {}
protected:
   std::string str;
};

class FlagOption: public OptionBase
{
public:
   FlagOption(const std::string& s) : OptionBase(s) {}
   void Process() override { ... do stuff here ... } 
};

class HashOption: public OptionBase
{
public:
    HashOption(const std::string& s) : OptionBase(s) {}
    void Process() override { ... do has stuff here ... }
};


class FunctionName: public OptoonBase
{
    ... you get the idea ... 
};

Now you can "process" all your OptionBase type things in a consistent manner, calling the same processing function on each of them. But I'm not sure that's what you were looking for.

Mats Petersson
  • 126,704
  • 14
  • 140
  • 227