Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
eth0.n
Jun 1, 2012

VikingofRock posted:

Wait, what's wrong with that code? I checked the misc-inaccurate-erase documentation but I think I must still be missing something.

Needs the end iterator as second argument to erase. As is, it only erases a single entry, not all applicable.

Adbot
ADBOT LOVES YOU

VikingofRock
Aug 24, 2008




eth0.n posted:

Needs the end iterator as second argument to erase. As is, it only erases a single entry, not all applicable.

Ooooh. Okay yeah that makes sense. Thanks.

Ralith
Jan 12, 2011

I see a ship in the harbor
I can and shall obey
But if it wasn't for your misfortune
I'd be a heavenly person today
For Emacs, I like rtags, which (unlike YCM or any other C++ magic layer I've tried) pulls all your compiler flags and options from your build system, so finding the right headers and so forth Just Works without any manual configuration.

Xarn
Jun 26, 2015

b0lt posted:

Are you using a sufficiently new version?

code:
~$ clang-tidy --version
LLVM ([url]http://llvm.org/[/url]):
  LLVM version 3.8.0
  
  Optimized build.
  Built Apr 21 2016 (14:10:42).
  Default target: x86_64-pc-linux-gnu
  Host CPU: broadwell
I would hope so.

quiggy
Aug 7, 2010

[in Russian] Oof.


Possibly stupid question: I noticed a change that my boss has been making to my code. Namely, I'll define some debug macro at the top of my file, like

code:
#define THISFILENAME_DEBUG_
And then I'll test that if #ifdef. I noticed that he's changed that to

code:
#if defined (THISFILENAME_DEBUG_)
Is there a difference between the two? Maybe it's compiler specific between gcc and MSVC?

e: this is C++03, if it makes a difference.

Star War Sex Parrot
Oct 2, 2003

Does this correspond to what you're seeing?

https://www.iar.com/support/resources/articles/advanced-preprocessor-tips-and-tricks/

Specifically the section "Why you should prefer #ifs over #ifdefs"

quiggy
Aug 7, 2010

[in Russian] Oof.


Star War Sex Parrot posted:

Does this correspond to what you're seeing?

https://www.iar.com/support/resources/articles/advanced-preprocessor-tips-and-tricks/

Specifically the section "Why you should prefer #ifs over #ifdefs"

Except there's also the defined keyword in there. I would think that if someone goes and sets #define THISFILENAME_DEBUG_ 0 that'll still return true when testing #if defined

Klades
Sep 8, 2011

I think the main difference between #ifdef and #if defined is that you can't do #ifdef THING1 && THING2 but you can do #if defined (THING1) && defined (THING2)

quiggy
Aug 7, 2010

[in Russian] Oof.


Klades posted:

I think the main difference between #ifdef and #if defined is that you can't do #ifdef THING1 && THING2 but you can do #if defined (THING1) && defined (THING2)

Digging into it more it does appear that you're right, the only real difference is that #if defined allows you to use Boolean operators. Not sure why they didn't just bake that into #ifdef somewhere along the line but I'm sure there are reasons far beyond my understanding.

fankey
Aug 31, 2001

This code
code:
#include <sstream>

int main()
{
	std::ostringstream os1, os2;
	os1 << "this " << "is " << "a " << "test";
	os2 << os1;
	return 0;
}
compiles fine in VS2010, VS2012 and g++ 4.8.1 but fails in VS2015 with the error
code:
error C2678: binary '<<': no operator found which takes a left-hand operand of type 'std::ostringstream' (or there is no acceptable conversion)
Is this just a bug in VS2015? It looks like <ostream> contains a suitable override with
code:
	_Myt& __CLR_OR_THIS_CALL operator<<(_Myt& (__cdecl *_Pfn)(_Myt&))
		{	// call basic_ostream manipulator
		_DEBUG_POINTER(_Pfn);
		return ((*_Pfn)(*this));
		}

eth0.n
Jun 1, 2012

fankey posted:

Is this just a bug in VS2015? It looks like <ostream> contains a suitable override with
code:
	_Myt& __CLR_OR_THIS_CALL operator<<(_Myt& (__cdecl *_Pfn)(_Myt&))
		{	// call basic_ostream manipulator
		_DEBUG_POINTER(_Pfn);
		return ((*_Pfn)(*this));
		}

That overload takes a function pointer, not a stream. It's meant for stream manipulators, which take a stream as argument, and return one as well (normally the same one).

As far as I know, there's no standard overload for streaming an ostream into an ostream. Sounds like non-standard behavior in those other compilers.

netcat
Apr 29, 2008
I believe that was a bug in earlier compilers (or just lax standard compliance), it should fail in later versions of gcc as well: http://ideone.com/WBOs5r

Vanadium
Jan 8, 2005

Isn't it like os2 << os1.rdbuf();

Klades
Sep 8, 2011

Vanadium posted:

Isn't it like os2 << os1.rdbuf();

In this particular case you could do os2 << os1.str(), I believe.

Plorkyeran
Mar 22, 2007

To Escape The Shackles Of The Old Forums, We Must Reject The Tribal Negativity He Endorsed
That would be significantly slower (it would copy the buffer to a string and then into the receivers buffer rather than copying directly between them).

eth0.n
Jun 1, 2012

Vanadium posted:

Isn't it like os2 << os1.rdbuf();

Doesn't work: http://ideone.com/fcdCvD

I believe the problem is os1 is an output stream, but you're trying to use it as a source of input.

With stringstream (which goes both ways), it works, but it's kind of inconsistent it looks like. Extracting from the same stream twice doesn't seem to work. I'm guessing there's an internal position or EOF flag that has to get reset.

Xarn
Jun 26, 2015
C++17 is shaping up to be interesting even with modules and concepts removed.

code:
if (auto [a, b] = foo(); a.valid()) {
    // do stuff with a, b
} else {
    // do different stuff with a, b
}
:2bong:

roomforthetuna
Mar 22, 2005

I don't need to know anything about virii! My CUSTOM PROGRAM keeps me protected! It's not like they'll try to come in through the Internet or something!

Xarn posted:

C++17 is shaping up to be interesting even with modules and concepts removed.

code:
if (auto [a, b] = foo(); a.valid()) {
    // do stuff with a, b
} else {
    // do different stuff with a, b
}
:2bong:
Neat, looks like it's probably copied the annoying flaw from Go, where you often want to be able to do the following but can't... (mapped to resemble this new C++ syntax)
code:
string a;
if ([a, auto b] = foo(); b) {
  // log that b was true, maybe
} else {
  // log that b was false
  a = "butts";
}
// do stuff with the value of a that persists outside of that scope, but b is gone now.
It really irks me that Go chose to make the operator the decision-maker on whether return values are pre-existing or auto-assigned and scoped, rather than having this be something tied to the variable declaration.
ie. they did
code:
x, y := butts()
vs.
var x int; var y bool; x, y = butts()
rather than eg.
code:
^x, ^y = butts()
vs.
var x int; var y bool; x, y = butts()
which would have allowed half and half
var x int; x, ^y = butts()
Edit: Choice of ^ completely arbitrary, this could be pretty much any character since the only operator that presently makes sense in this context is *. So it could be #x or +x or !x or @x or $x or :x to mean "create a variable named x in this scope whose type is determined from what's assigned to it". (vs. the actual implementation of := to mean "everything on the left is created in this scope except if it already exists in *exactly* this scope, but if it exists in this scope from a wider scope create a new one anyway and hide the wider one", which is just awful.)

roomforthetuna fucked around with this message at 19:12 on Jun 26, 2016

Plorkyeran
Mar 22, 2007

To Escape The Shackles Of The Old Forums, We Must Reject The Tribal Negativity He Endorsed
The destructuring syntax is deliberately super-constrained to avoid having it limit what syntax is available for pattern matching, and once that proposal is further along they expect to be able to also make destructuring more flexible.

Olly the Otter
Jul 22, 2007
I'm trying to figure out why simple composition involving lambdas and std::bind completely falls apart. For example, why does this not compile?

code:
#include <iostream>
#include <functional>
#include <string>

int main(void)
{
	std::string text("Hello, world!");
	auto say = [](const std::string &what) { std::cout << what << std::endl; };
	say(text);  // works
	auto job = std::bind(say, text);
	job();      // works
	using JobType = decltype(job);
	auto run = [](const JobType &task) { task(); };
	run(job);   // works
	auto wrapper = std::bind(run, job);
	wrapper(); // error: no matching function for call to object
	           // of type 'std::_Bind<<lambda at bind_test.cpp:13:13>
	return 0;
}
clang++, g++, and Visual Studio are all in agreement that this not valid, but for the life of me I can't understand why.

I know that these days it's recommended to just use lambdas for everything and keep clear of std::bind. But I'm stuck with a C++11 compiler for a while and can't do move-capture with lambdas, so sometimes I have to emulate it using std::bind. (My actual use case somewhat resembles the above, but with a move-only value instead of a std::string and with the appropriate calls to std::move added. But the compile error happens even without that.)

Can anyone tell me what's going on here?

GeneralZod
May 28, 2003

Kneel before Zod!
Grimey Drawer

Olly the Otter posted:

I'm trying to figure out why simple composition involving lambdas and std::bind completely falls apart. For example, why does this not compile?

code:
#include <iostream>
#include <functional>
#include <string>

int main(void)
{
	std::string text("Hello, world!");
	auto say = [](const std::string &what) { std::cout << what << std::endl; };
	say(text);  // works
	auto job = std::bind(say, text);
	job();      // works
	using JobType = decltype(job);
	auto run = [](const JobType &task) { task(); };
	run(job);   // works
	auto wrapper = std::bind(run, job);
	wrapper(); // error: no matching function for call to object
	           // of type 'std::_Bind<<lambda at bind_test.cpp:13:13>
	return 0;
}
clang++, g++, and Visual Studio are all in agreement that this not valid, but for the life of me I can't understand why.

I know that these days it's recommended to just use lambdas for everything and keep clear of std::bind. But I'm stuck with a C++11 compiler for a while and can't do move-capture with lambdas, so sometimes I have to emulate it using std::bind. (My actual use case somewhat resembles the above, but with a move-only value instead of a std::string and with the appropriate calls to std::move added. But the compile error happens even without that.)

Can anyone tell me what's going on here?

I'm not following the details exactly, but it looks like you might be running into something like this:

http://stackoverflow.com/questions/10777421/stdbind-a-bound-function

and one of the suggested fixes - using

code:
 std::function<void()> job =  <blah>
rather than

code:
auto job =  <blah>
seems to work.

Not a very satisfying answer, but hopefully it will point someone towards something better :)

Edit:

Here's a little more info:

quote:

If the stored argument arg is of type T for which std::is_bind_expression<T>::value == true (meaning, another bind expression was passed directly into the initial call to bind), then bind performs function composition:instead of passing the function object that the bind subexpression would return, the subexpression is invoked eagerly, and its return value is passed to the outer invokable object. If the bind subexpression has any placeholder arguments, they are shared with the outer bind (picked out of u1, u2, ...). Specifically, the argument vn in the std::invoke call above is arg(std::forward<Uj>(uj)...) and the type Vn in the same call is std::result_of_t<T cv &(Uj&&...)>&& (cv qualification is the same as that of g).

So essentially, in std::bind(run, job), it seems that job, if it is declared as auto and thus deduced as a bind expression, gets evaluated eagerly and the result - which is of type void - passed through, causing mayhem.

Edit2:

Going back to your original code but with

code:
    auto wrapper = std::bind(run, protect(job));
using the definition of "protect" given here seems to work.

GeneralZod fucked around with this message at 18:51 on Jun 29, 2016

Dr Monkeysee
Oct 11, 2002

just a fox like a hundred thousand others
Nap Ghost

eth0.n posted:

noop deleter is probably the best of all these bad options, though. No good options when dealing with such a fundamental design flaw...

Edit: except, isn't the deleter type part of the static type of a unique_ptr? So if the classes can't be changed, is noop deleter actually possible?

This is from a couple weeks back but you called the Toilet and Urinal ownership semantics a "design flaw" but I'm curious what the right design approach is here. With any class you have to make a concrete choice about the ownership semantics of constructor parameters, right? It seems untenable for every class to declare both owning and non-owning semantics for its data members.

Am I missing something specific about this case that makes the use of unique_ptr bad? I'm not super fluent in C++ so I may just be confused.

edit: like, changing the params to shared_ptr would fix the problem but that assumes both classes must take each other (and anyone else who may need that interface as a member) into account as part of their design. Again, seems untenable in the general case.

Dr Monkeysee fucked around with this message at 06:40 on Jul 1, 2016

Klades
Sep 8, 2011

Dr Monkeysee posted:

This is from a couple weeks back but you called the Toilet and Urinal ownership semantics a "design flaw" but I'm curious what the right design approach is here. With any class you have to make a concrete choice about the ownership semantics of constructor parameters, right? It seems untenable for every class to declare both owning and non-owning semantics for its data members.

Am I missing something specific about this case that makes the use of unique_ptr bad? I'm not super fluent in C++ so I may just be confused.

edit: like, changing the params to shared_ptr would fix the problem but that assumes both classes must take each other (and anyone else who may need that interface as a member) into account as part of their design. Again, seems untenable in the general case.

If I'm understanding the problem correctly, the issue is that both Urinal and Toilet want to have unique ownership of a GenitalInterface that they're actually sharing.
If it is actually the case that both of those classes need to own the GenitalInterface that they're using, then I think shared_ptr would be appropriate and would eliminate the need for any hacky workarounds. However, it might also be a bad design. Maybe something else should own the GenitalInterface and Urinal and Toilet just hold non-owning references to it (i.e. raw pointers that you don't delete). Without knowing anything more about the actual matter at hand, I think the latter approach is generally preferable.

netcat
Apr 29, 2008
In my experience it's pretty rare to need shared ownership so I almost always use unique_ptr. This codebase however is littered with shared_ptr with no real thought of pointer ownership

Illusive Fuck Man
Jul 5, 2004
RIP John McCain feel better xoxo 💋 🙏
Taco Defender

Illusive gently caress Man posted:

say you have GenitalInterface, and two classes (Toilet and Urinal) that take ownership of unique_ptr<GenitalInterface> in their constructor. I am not able to change those classes to give up ownership. I now need to use a single instance of an implementation of GenitalInterface.

I didn't describe the problem entirely accurately - Toilet could be better described as PenisHolder or maybe MaleUrinarySystem, and didn't take a GenitalInterface in its constructor, but created a Penis internally and had a function GenitalInterface* getGenitalInterface(); My solution was changing Urinal to give up ownership, (taking raw GenitalInterface* in the constructor). The author pushed back a little but :shrug:.

I do feel like the original design for Urinal taking a unique_ptr was a design flaw. In my experience, if your class requires a pointer to an interface (and not any specific implementation), it very often doesn't need ownership at all. This can also uncomplicate mocking things for unit tests.

Klades posted:

Maybe something else should own the GenitalInterface and Urinal and Toilet just hold non-owning references to it (i.e. raw pointers that you don't delete). Without knowing anything more about the actual matter at hand, I think the latter approach is generally preferable.

yeah this

eth0.n
Jun 1, 2012

Dr Monkeysee posted:

This is from a couple weeks back but you called the Toilet and Urinal ownership semantics a "design flaw" but I'm curious what the right design approach is here. With any class you have to make a concrete choice about the ownership semantics of constructor parameters, right? It seems untenable for every class to declare both owning and non-owning semantics for its data members.

Sure, but in this case, the wrong choice was made. Exclusive ownership was chosen for an object that now requires shared ownership. It's possible it was the right choice given the info at the time, but it seems more likely that someone didn't think through the consequences of using unique_ptr, and what the objects were meant to represent. In any event, regardless of blame, now it's a design flaw that is quite difficult to work around.

quote:

edit: like, changing the params to shared_ptr would fix the problem but that assumes both classes must take each other (and anyone else who may need that interface as a member) into account as part of their design. Again, seems untenable in the general case.

Not sure I follow. Why would the shared owners of the shared_ptr require any awareness of each other, which they wouldn't require with any other means of sharing access to an object?

As others have mentioned, passing plain non-owning pointers to these classes, and holding ownership somewhere else, seems likely the best alternative. Much like recursive locks, shared_ptr is too often used as a way to simply punt on making important design decisions.

Dr Monkeysee
Oct 11, 2002

just a fox like a hundred thousand others
Nap Ghost

eth0.n posted:

Not sure I follow. Why would the shared owners of the shared_ptr require any awareness of each other, which they wouldn't require with any other means of sharing access to an object?

As others have mentioned, passing plain non-owning pointers to these classes, and holding ownership somewhere else, seems likely the best alternative. Much like recursive locks, shared_ptr is too often used as a way to simply punt on making important design decisions.

They require awareness of each other to the degree that they are deciding they're not the sole owners; that shared ownership is a possibility. My point was when could you ever choose unique_ptr if you always must anticipate the possibility that tomorrow someone else might want ownership as well.

Anyway the additional details clarified somewhat. I was picturing the original problem as a dependency injection scenario for both classes where ownership is particularly relevant but sounds like it's a bit more complicated than that. Having a third component be the true owner is an interesting idea.

edit: in fact it sounds like in this case Toilet is the true owner and is designed correctly (though maybe should have returned a reference instead of a pointer to make it even more obvious) but Urinal should have just taken a raw pointer, which is what ended up being the fix. Anyway thanks for the answers.

edit2: I suppose if it was all about DI then the DI container would be the owner of everything and just about every dependent class would take non-owning pointers or references. My background is mostly C# where I'm used to thinking about dependencies but not ownership.

Dr Monkeysee fucked around with this message at 08:06 on Jul 2, 2016

Chuu
Sep 11, 2004

Grimey Drawer
Here's a very dumb problem that is stumping me. I ran into the following bug in a codebase that I have to maintain:

m_Connections is a std::map<int, std::shared_ptr<Connection>> that is mapping file descriptors to connections. We have the following code to check if a connection is the last in a connection pool:

code:
    //m_Connections is a std::map<int, std::shared_ptr<Connection>>

    bool Pool::isLastConnection(const Connection *connection) const {
        return (m_Connections.size() == 1 && m_Connections.begin()->second.get() == connection);
    }

This always returns false, specifically the map iterator that we deference has garbage values for both the key and value. The following re-factoring does work though:

code:
    bool Pool::isLastConnection(const Connection *connection) const {
        if(m_Connections.size() == 1){
            const auto& sharedPtr = m_Connections.begin()->second;
            return sharedPtr.get() == connection;
        }
        return false;
    }

What's going wrong?

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe
I don't see anything wrong with that code. What compiler are you using? Temporaries in conditionally-executed code are actually surprisingly tricky for the compiler, and if this is some out-of-date or unstable release, it's possible that it just gets them wrong. (std::map::iterator is a non-trivial type.)

Chuu
Sep 11, 2004

Grimey Drawer

rjmccall posted:

I don't see anything wrong with that code. What compiler are you using? Temporaries in conditionally-executed code are actually surprisingly tricky for the compiler, and if this is some out-of-date or unstable release, it's possible that it just gets them wrong. (std::map::iterator is a non-trivial type.)

GCC 5.2.1 with libstdc++ 6.0.19. Basically redhat's devtools-4 toolchain. Also for what it's worth, valgrind has no issues with the executable.

Why are temporaries in conditional code tricky?

Chuu fucked around with this message at 03:10 on Jul 6, 2016

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe

Chuu posted:

GCC 5.2.1 with libstdc++ 6.0.19. Basically redhat's devtools-4 toolchain. Also for what it's worth, valgrind has no issues with the executable.

Why are temporaries in conditional code tricky?

Temporaries have to be destroyed in reverse order of construction at the end of the full-expression, regardless of conditionality, so e.g. if you create a temporary, enter conditional code, create a new temporary, leave conditional code, and then create a third temporary (e.g. foo(A(), flag ? A().whatever() : 0, A())), then the unconditional code at the end of the expression has to destroy the third, conditionally destroy the second, and then destroy the first. It's not a hard problem, but it is added complexity that can sometimes be a source of bugs.

I would not expect that kind of bug in a stable release of GCC, though, so I have no idea.

seiken
Feb 7, 2005

hah ha ha
Seems more likely that you are doing something undefined somewhere else nearby that just so happens to work out fine in the second version.

Cory Parsnipson
Nov 15, 2015
I want to make a Node class to build trees and have a template parameter "HasParentPointers" that will let me enable parent pointers or not in the Node class. Is it possible, when HasParentPointers is false, to have the Node class not even contain a pointer member? Basically, I want to use templates to make a node class that either has a parent * member or not.

Joda
Apr 24, 2010

When I'm off, I just like to really let go and have fun, y'know?

Fun Shoe

Cory Parsnipson posted:

I want to make a Node class to build trees and have a template parameter "HasParentPointers" that will let me enable parent pointers or not in the Node class. Is it possible, when HasParentPointers is false, to have the Node class not even contain a pointer member? Basically, I want to use templates to make a node class that either has a parent * member or not.

Just use nullptr if it doesn't have a parent? Then do an if(parent == nullptr) to check. Having a varying number of fields in your class is pretty dodgy, and probably involved just creating a new node to replace the old one and using polymorphism. If you need the flag, just have the pointer field anyway.

Joda fucked around with this message at 06:45 on Jul 8, 2016

Klades
Sep 8, 2011

Cory Parsnipson posted:

I want to make a Node class to build trees and have a template parameter "HasParentPointers" that will let me enable parent pointers or not in the Node class. Is it possible, when HasParentPointers is false, to have the Node class not even contain a pointer member? Basically, I want to use templates to make a node class that either has a parent * member or not.

If you really have to have two versions of your nodes, I think you should make a Node class without parent pointers, and then a ParentPointerNode class that inherits from Node. I think you can do what you're asking with template specializations, but I don't think it's a good idea.

There's also other things you could do that didn't involve making multiple node classes, like using the state pattern, but using anything like that would probably be abusing something or other and resulting in an ugly mess. You should either use inheritance or just check for nullptr like Joda said.

Cory Parsnipson
Nov 15, 2015
Yeah, it's pretty simple if I use a normal conditional and a null pointer. I've been experimenting with template programming recently and I was wondering if there was a nice way to do it with templates, especially if it would remove the overhead of having a pointer member and executing if statements if I disabled parent pointers.

I'm really excited about parameterizing everything with templates now...

Klades
Sep 8, 2011

Cory Parsnipson posted:

Yeah, it's pretty simple if I use a normal conditional and a null pointer. I've been experimenting with template programming recently and I was wondering if there was a nice way to do it with templates, especially if it would remove the overhead of having a pointer member and executing if statements if I disabled parent pointers.

I'm really excited about parameterizing everything with templates now...

If you really want to knock out the if statements (not necessarily a bad idea if they're going to be gone over a lot) you might consider just using a visitor instead of trying to be fancy with templates.

Trying to be too fancy with templates makes your code a nightmare to maintain.

Xarn
Jun 26, 2015

Cory Parsnipson posted:

I want to make a Node class to build trees and have a template parameter "HasParentPointers" that will let me enable parent pointers or not in the Node class. Is it possible, when HasParentPointers is false, to have the Node class not even contain a pointer member? Basically, I want to use templates to make a node class that either has a parent * member or not.

Assuming that you want to have a tree of either nodes with parent pointers, or nodes without parent pointers, not mix and match, it is definitely possible, but kinda painful in C++14 (and pre C++14 as well). You will end up writing the code twice, using template specialization.

It should be p. easy in C++17, but thats not quite out or production ready yet.

leper khan
Dec 28, 2010
Honest to god thinks Half Life 2 is a bad game. But at least he likes Monster Hunter.

Xarn posted:

Assuming that you want to have a tree of either nodes with parent pointers, or nodes without parent pointers, not mix and match, it is definitely possible, but kinda painful in C++14 (and pre C++14 as well). You will end up writing the code twice, using template specialization.

It should be p. easy in C++17, but thats not quite out or production ready yet.

I think you could do it with the policy based design template template parameters outlined in Alexandrescu's Modern C++. Is that what you're referring to, and if so what's coming in 17 (I haven't been keeping up)?

Adbot
ADBOT LOVES YOU

Xarn
Jun 26, 2015

leper khan posted:

I think you could do it with the policy based design template template parameters outlined in Alexandrescu's Modern C++. Is that what you're referring to, and if so what's coming in 17 (I haven't been keeping up)?

I would just write the two specializations, as I see policies as useful for user customization without combinatoric explosion if you have more parameters, but there isn't much user customization to be done here.

code:
template <bool hasParentPointer>
struct node;

template <>
struct node<true> {
    node* parent = nullptr;
    node* left_child = nullptr;
    node* right_child = nullptr;
    int data;
};

template <>
struct node<false> {
    node* left_child = nullptr;
    node* right_child = nullptr;
    int data;
};

class large_tree {
public:
    size_t size() const {
        return sz;
    }
private:
    node<true>* root = nullptr;
    size_t sz;
};

class small_tree {
public:
    size_t size() const {
        return sz;
    }
private:
    node<false>* root = nullptr;
    size_t sz;
};

int main() {
    large_tree lt;
    small_tree st;
}

You will end up with some boilerplate and a whole lot of writing very similar code.

C++17 adds
code:
if constexpr {...}
As far as I know, you cannot use it to modify which class members are present in a class, but it should still save you a lot of repeated code and boilerplate, when used like this:

code:
class parametric_tree<bool hasParentPointer> {
public:
    using node_type = node<hasParentPointer>;

    size_t size() const {
        return sz; // always the same
    }
    
    void print() const {
        if constexpr(hasParentPointer) {
            // use parent pointer while traversing the tree
        } else {
            // cant use parent pointer while traversing the tree
        }
    }
    
    void insert(int elem) {
        // find place to insert the element...
        
        auto* new_node = new node_type{};
        new_node.data = elem;
        insert_at = new_node;
        if constexpr(hasParentPointer) {
            new_node.parent = ...;
        }
    }


private:
    node_type* root = nullptr;    
    size_t sz;
};

There are some pre C++17 ways of deduplicating the code, but it always felt like causing more problems, than solving. Also note that I don't have access to C++17 compiler, so take the above with a grain of salt.

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply