Define class Identifier and template TaggedIdentifier...

... Identifier holds strings used for internal purposes and not shown to users;
TaggedIdentifier generates subclasses of Identifier for different purposes,
which won't implicitly (that is, inadvertently) interconvert as function
arguments.
This commit is contained in:
Paul Licameli 2019-02-26 14:50:35 -05:00
parent 172c8abe03
commit 3eeb91f23a
4 changed files with 222 additions and 4 deletions

View File

@ -46,11 +46,9 @@
#include <type_traits>
#include <vector>
#include <wx/debug.h> // for wxASSERT
#include <wx/string.h> // type used in inline function
#include <wx/string.h> // type used in inline function and member variable
#include <wx/version.h> // for wxCHECK_VERSION
class wxString;
#if !wxCHECK_VERSION(3, 1, 0)
// For using std::unordered_map on wxString
namespace std
@ -73,6 +71,199 @@ namespace std
// until proper public headers are created for the stuff in here.
// ----------------------------------------------------------------------------
// An explicitly nonlocalized string, not meant for the user to see.
// String manipulations are discouraged, other than splitting and joining on
// separator characters.
// Wherever GET is used to fetch the underlying wxString, there should be a
// comment explaining the need for it.
class Identifier
{
public:
Identifier() = default;
// Allow implicit conversion to this class, but not from
Identifier(const wxString &str) : value{ str } {}
// Allow implicit conversion to this class, but not from
Identifier(const wxChar *str) : value{ str } {}
// Allow implicit conversion to this class, but not from
Identifier(const char *str) : value{ str } {}
// Copy construction and assignment
Identifier( const Identifier & ) = default;
Identifier &operator = ( const Identifier& ) = default;
// Move construction and assignment
Identifier( wxString && str )
{ value.swap( str ); }
Identifier( Identifier && id )
{ swap( id ); }
Identifier &operator= ( Identifier&& id )
{
if ( this != &id )
value.clear(), swap( id );
return *this;
}
// Implements moves
void swap( Identifier &id ) { value.swap( id.value ); }
// Convenience for building concatenated identifiers.
// The list must have at least two members
// (so you don't easily circumvent the restrictions on interconversions
// intended in TaggedIdentifier below)
explicit
Identifier(std::initializer_list<Identifier> components, wxChar separator);
bool empty() const { return value.empty(); }
size_t size() const { return value.size(); }
size_t length() const { return value.length(); }
// Explicit conversion to wxString, meant to be ugly-looking and
// demanding of a comment why it's correct
const wxString &GET() const { return value; }
std::vector< Identifier > split( wxChar separator ) const;
private:
wxString value;
};
// Comparisons of Identifiers are case-sensitive
inline bool operator == ( const Identifier &x, const Identifier &y )
{ return x.GET() == y.GET(); }
inline bool operator != ( const Identifier &x, const Identifier &y )
{ return !(x == y); }
inline bool operator < ( const Identifier &x, const Identifier &y )
{ return x.GET() < y.GET(); }
inline bool operator > ( const Identifier &x, const Identifier &y )
{ return y < x; }
inline bool operator <= ( const Identifier &x, const Identifier &y )
{ return !(y < x); }
inline bool operator >= ( const Identifier &x, const Identifier &y )
{ return !(x < y); }
namespace std
{
template<> struct hash< Identifier > {
size_t operator () ( const Identifier &id ) const // noexcept
{ return hash<wxString>{}( id.GET() ); }
};
}
// This lets you pass Identifier into wxFileConfig::Read
inline bool wxFromString(const wxString& str, Identifier *id)
{ if (id) { *id = str; return true; } else return false; }
// This lets you pass Identifier into wxFileConfig::Write
inline wxString wxToString( const Identifier& str ) { return str.GET(); }
// Template parameter allows generation of different TaggedIdentifier classes
// that don't interconvert implicitly
// The second template parameter determines whether comparisons are case
// sensitive; default is case sensitive
template<typename Tag, bool CaseSensitive = true >
class TaggedIdentifier : public Identifier
{
public:
using TagType = Tag;
using Identifier::Identifier;
TaggedIdentifier() = default;
// Allowed for the same Tag class and case sensitivity
TaggedIdentifier( const TaggedIdentifier& ) = default;
TaggedIdentifier( TaggedIdentifier&& ) = default;
TaggedIdentifier& operator= ( const TaggedIdentifier& ) = default;
TaggedIdentifier& operator= ( TaggedIdentifier&& ) = default;
// Prohibited for other Tag classes or case sensitivity
template< typename Tag2, bool b >
TaggedIdentifier( const TaggedIdentifier<Tag2, b>& ) = delete;
template< typename Tag2, bool b >
TaggedIdentifier( TaggedIdentifier<Tag2, b>&& ) = delete;
template< typename Tag2, bool b >
TaggedIdentifier& operator= ( const TaggedIdentifier<Tag2, b>& ) = delete;
template< typename Tag2, bool b >
TaggedIdentifier& operator= ( TaggedIdentifier<Tag2, b>&& ) = delete;
// Allow implicit conversion to this class from un-tagged Identifier,
// but not from; resolution will use other overloads above if argument
// has a tag
TaggedIdentifier(const Identifier &str) : Identifier{ str } {}
// Conversion to another kind of TaggedIdentifier
template<typename String, typename = typename String::TagType>
String CONVERT() const
{ return String{ this->GET() }; }
};
// Comparison of a TaggedIdentifier with an Identifier is allowed, resolving
// to one of the operators on Identifiers defined above, but always case
// sensitive.
// Comparison operators for two TaggedIdentifers, below, require the same tags
// and case sensitivity.
template< typename Tag1, typename Tag2, bool b1, bool b2 >
inline bool operator == (
const TaggedIdentifier< Tag1, b1 > &x, const TaggedIdentifier< Tag2, b2 > &y )
{
static_assert( std::is_same< Tag1, Tag2 >::value && b1 == b2,
"TaggedIdentifiers with different tags or sensitivity are not comparable" );
// This test should be eliminated at compile time:
if ( b1 )
return x.GET(). Cmp ( y.GET() ) == 0;
else
return x.GET(). CmpNoCase ( y.GET() ) == 0;
}
template< typename Tag1, typename Tag2, bool b1, bool b2 >
inline bool operator != (
const TaggedIdentifier< Tag1, b1 > &x, const TaggedIdentifier< Tag2, b2 > &y )
{ return !(x == y); }
template< typename Tag1, typename Tag2, bool b1, bool b2 >
inline bool operator < (
const TaggedIdentifier< Tag1, b1 > &x, const TaggedIdentifier< Tag2, b2 > &y )
{
static_assert( std::is_same< Tag1, Tag2 >::value && b1 == b2,
"TaggedIdentifiers with different tags or sensitivity are not comparable" );
// This test should be eliminated at compile time:
if ( b1 )
return x.GET(). Cmp ( y.GET() ) < 0;
else
return x.GET(). CmpNoCase ( y.GET() ) < 0;
}
template< typename Tag1, typename Tag2, bool b1, bool b2 >
inline bool operator > (
const TaggedIdentifier< Tag1, b1 > &x, const TaggedIdentifier< Tag2, b2 > &y )
{ return y < x; }
template< typename Tag1, typename Tag2, bool b1, bool b2 >
inline bool operator <= (
const TaggedIdentifier< Tag1, b1 > &x, const TaggedIdentifier< Tag2, b2 > &y )
{ return !(y < x); }
template< typename Tag1, typename Tag2, bool b1, bool b2 >
inline bool operator >= (
const TaggedIdentifier< Tag1, b1 > &x, const TaggedIdentifier< Tag2, b2 > &y )
{ return !(x < y); }
namespace std
{
template<typename Tag, bool b > struct hash< TaggedIdentifier<Tag, b> >
: hash< Identifier > {};
}
/**************************************************************************//**
\brief type alias for identifying a Plugin supplied by a module, each module

View File

@ -310,3 +310,25 @@ wxArrayStringEx LocalizedStrings(
std::mem_fn( &EnumValueSymbol::Translation )
);
}
// Find a better place for this?
#include "audacity/Types.h"
Identifier::Identifier(
std::initializer_list<Identifier> components, wxChar separator )
{
if( components.size() < 2 )
{
wxASSERT( false );
return;
}
auto iter = components.begin(), end = components.end();
value = (*iter++).value;
while (iter != end)
value += separator + (*iter++).value;
}
std::vector< Identifier > Identifier::split( wxChar separator ) const
{
auto strings = ::wxSplit( value, separator );
return { strings.begin(), strings.end() };
}

View File

@ -21,7 +21,6 @@
class wxArrayString;
class wxArrayStringEx;
class wxString;
extern AUDACITY_DLL_API const wxString& GetCustomTranslation(const wxString& str1 );
extern AUDACITY_DLL_API const wxString& GetCustomSubstitution(const wxString& str1 );

View File

@ -30,6 +30,12 @@ class AUDACITY_DLL_API XMLWriter /* not final */ {
virtual void StartTag(const wxString &name);
virtual void EndTag(const wxString &name);
// nonvirtual pass-through
void WriteAttr(const wxString &name, const Identifier &value)
// using GET once here, permitting Identifiers in XML,
// so no need for it at each WriteAttr call
{ WriteAttr( name, value.GET() ); }
virtual void WriteAttr(const wxString &name, const wxString &value);
virtual void WriteAttr(const wxString &name, const wxChar *value);