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
Xeom
Mar 16, 2007

Xerophyte posted:

Not sure I understand what the problem is. If you want
code:
if(is_same<desired_type, actual_type>::value)
  func_for_desired_type(type_value);
else
  func_for_other_types(type_value);
to select the branch at compile time then it already does that as written. std::is_same<T, U>::value is a static constant so there wont be a branch after constant folding and dead code elimination. Making a template specialization might be nicer, depending on where this appears, but you don't have to.

The problem was that the functions took two different types.

Absurd Alhazred posted:

Well, you could use std::is_integral<T>::value. It's a constexpr so if statements with it are handled at compile time, I think.

Wouldn't work this will have to branch out to several options. You just run into the same problem as before.

Adbot
ADBOT LOVES YOU

Xeom
Mar 16, 2007

rjmccall posted:

If your problem is that the code on one of the paths doesn't actually compile when the type is int or whatever, that's exactly the use case of C++17's constexpr if.

Now that seems like a much better option.

Xeom fucked around with this message at 20:55 on Jul 20, 2020

peepsalot
Apr 24, 2007

        PEEP THIS...
           BITCH!

Hey so I don't have a much experience with proper C++ Exception handling, but its seems like its Really Bad?

My problem is I want to throw from some class operators when they are given bad operands, and catch it at a higher level where I can provide better context in a message to the user.
But as far as I understand from some quick searches, there's no way to verify at compile time that exceptions would be handled for a given function/operator?

I mean, I can temporarily delete my operators definitions and try to compile, which would kinda help me find all the places they are used in code, then make sure the proper try/catch are in place, but is there really no way to protect in the future when someone else who doesn't know about these exceptions goes and adds more calls to these operators?

Foxfire_
Nov 8, 2010

Congratulations, you have discovered why exceptions are a terrible design idea and should essentially never be used! This isn't really a C++ thing, exceptions are bad in all languages.

Return errors and have explicit visible logic instead of using exceptions to do nonlocal jumps.

Absurd Alhazred
Mar 27, 2010

by Athanatos

Foxfire_ posted:

Congratulations, you have discovered why exceptions are a terrible design idea and should essentially never be used! This isn't really a C++ thing, exceptions are bad in all languages.

Return errors and have explicit visible logic instead of using exceptions to do nonlocal jumps.

Works great as long as you never have to deal with hardware.

Bruegels Fuckbooks
Sep 14, 2004

Now, listen - I know the two of you are very different from each other in a lot of ways, but you have to understand that as far as Grandpa's concerned, you're both pieces of shit! Yeah. I can prove it mathematically.

peepsalot posted:

Hey so I don't have a much experience with proper C++ Exception handling, but its seems like its Really Bad?

My problem is I want to throw from some class operators when they are given bad operands, and catch it at a higher level where I can provide better context in a message to the user.
But as far as I understand from some quick searches, there's no way to verify at compile time that exceptions would be handled for a given function/operator?

I mean, I can temporarily delete my operators definitions and try to compile, which would kinda help me find all the places they are used in code, then make sure the proper try/catch are in place, but is there really no way to protect in the future when someone else who doesn't know about these exceptions goes and adds more calls to these operators?

that dude going on about exceptions being evil is onto something, but i would also avoid operator overloading precisely because it might save typing but can produce headaches like the above.

Foxfire_
Nov 8, 2010

Absurd Alhazred posted:

Works great as long as you never have to deal with hardware.

Not sure what you mean by this?

Hardware interrupts/exceptions are a completely separate concept from language level exception throwing. No one uses C++ exceptions in embedded or kernel code.

qsvui
Aug 23, 2003
some crazy thing

Bruegels Fuckbooks posted:

that dude going on about exceptions being evil is onto something, but i would also avoid operator overloading precisely because it might save typing but can produce headaches like the above.

eh both are fine, just have a really good reason

Computer viking
May 30, 2011
Now with less breakage.

Foxfire_ posted:

Congratulations, you have discovered why exceptions are a terrible design idea and should essentially never be used! This isn't really a C++ thing, exceptions are bad in all languages.

Return errors and have explicit visible logic instead of using exceptions to do nonlocal jumps.

At least the Java thing where you have to be much more explicit about them is differently bad.

peepsalot
Apr 24, 2007

        PEEP THIS...
           BITCH!

OK, I think I figured out a reasonable solution.

Short answer: Instead of throwing, I'm gonna use something kinda similar to Andrei Alexandrescu's Expected<T>.

Long answer: This relates to the same project that my questions itt are usually in regards to: a DSL interpreter, where Values are represented internally with a boost::variant. The issue here was that undefined operations (usually from mixing incompatible types in some expression) were returning an "undef" type which just made things harder to debug without any clear warnings or error messages about which piece of script caused the undef.
The Value type has various operators defined using boost::static_visitors, and is sort of the lowest level code, which is separate from the AST, such that it has no knowledge of source line location information.
Since Value is already a variant, I didn't want to wrap it in another layer of Expected<T>...

"undef" was previously represented with boost::blank, but I realized that its essentially the "unexpected" type, so it probably makes more sense for it to be an object which requires a message string, indicating some reason it got set to undef.
That way higher level code with access to AST info can deal with undefined return values by spitting out useful messages, and appending source file, line number info, etc.

ultrafilter
Aug 23, 2007

It's okay if you have any questions.


Is there a general stigma against using namespace std; or similar at global scope within a source file? None of the places I've worked have had an issue with it, but I've seen it mentioned as a bad thing and now I'm wondering.

(Obviously header files are a different story, so I'm specifically asking about source files.)

nielsm
Jun 1, 2009



Yes, importing an entire namespace wholesale, especially std, is an easy way to get all kinds of global symbols you didn't expect. The entire point of namespaces is to isolate symbols and avoid collisions.

Xerophyte
Mar 17, 2008

This space intentionally left blank

ultrafilter posted:

Is there a general stigma against using namespace std; or similar at global scope within a source file? None of the places I've worked have had an issue with it, but I've seen it mentioned as a bad thing and now I'm wondering.

Short answer: yes. In part to avoid spurious identifier collisions, in part to avoid programmer confusion.


Long answer: The rule is really to avoid using namespace foo for any namespace, since doing so always risks triggering some collision if the foo library updates its namespace with some new identifier. std just suffers especially since its functions and types are very often reimplemented in different libraries that use the same names, so it's more likely than most to cause collision issues.

The general advice is to try to be specific about what identifiers you're importing. Avoid importing entire namespaces, and avoid importing over large scopes. Importing the entirety of a large namespace, like std, for a large scope, such as an entire cpp file, is code smell to a lot of people. Doing things like using clock = std::chrono::high_resolution_clock in local scopes and for specific classes that use the date/time library is great.

Finally, something like plain vector<float> xs is ambiguous since you cannot immediately tell what vector is meant. Readers will need to track down the namespace import to understand the code, which is likewise harder if the import is general, for a large scope and far away from the declaration. std::vector<float> xs is immediately obvious.

It matters a lot what the local shop style is too of course. If everyone where you work is used to using namespace std being standard then changing it might be more confusing.

Xarn
Jun 26, 2015
Also often what you really want is using namespace::thing, rather than the whole namespace.

more falafel please
Feb 26, 2005

forums poster

Xarn posted:

Also often what you really want is using namespace::thing, rather than the whole namespace.

Yeah. If the standard library had been designed with nested namespaces to start, doing using namespace std::io; or similar would probably be fine, but std as it is is way too big.

Bruegels Fuckbooks
Sep 14, 2004

Now, listen - I know the two of you are very different from each other in a lot of ways, but you have to understand that as far as Grandpa's concerned, you're both pieces of shit! Yeah. I can prove it mathematically.

ultrafilter posted:

Is there a general stigma against using namespace std; or similar at global scope within a source file? None of the places I've worked have had an issue with it, but I've seen it mentioned as a bad thing and now I'm wondering.

(Obviously header files are a different story, so I'm specifically asking about source files.)

If you're working on a project where you have to include all sorts of header files like windows.h, using namespace std (even in the cpp files) will usually just lead to stuff not compiling because of namespace clashes, and that's the best possibility you can hope for. The worst outcome is that stuff compiles but something with the same name but different behavior but not different enough was defined in both poo poo.h and std, and it causes hard-to-troubleshoot unforeseen issues.

Xerophyte
Mar 17, 2008

This space intentionally left blank
I mean
C++ code:
#define NOGDICAPMASKS
#define NOVIRTUALKEYCODES
#define NOWINMESSAGES
#define NOWINSTYLES
#define NOSYSMETRICS
#define NOMENUS
#define NOICONS
#define NOKEYSTATES
#define NOSYSCOMMANDS
#define NORASTEROPS
#define NOSHOWWINDOW
#define NOATOM
#define NOCLIPBOARD
#define NOCOLOR
#define NOCTLMGR
#define NODRAWTEXT
#define NOGDI
#define NOKERNEL
#define NOUSER
#define NONLS
#define NOMB
#define NOMEMMGR
#define NOMETAFILE
#define NOMINMAX
#define NOMSG
#define NOOPENFILE
#define NOSCROLL
#define NOSERVICE
#define NOSOUND
#define NOTEXTMETRIC
#define NOWH
#define NOWINOFFSETS
#define NOCOMM
#define NOKANJI
#define NOHELP
#define NOPROFILER
#define NODEFERWINDOWPOS
#define NOMCX
#define WIN32_LEAN_AND_MEAN
#undef OEMRESOURCE

#include <windows.h>
It's difficult for code to touch windows.h and remain free of horror regardless of namespace practices, really.

Dren
Jan 5, 2001

Pillbug
As others have mentioned using namespace std can cause actual problems but even if it didn’t it still shouldn’t be used. Why not? Because explicit is better than implicit. Who among us really knows the standard library? Even a moment’s confusion about where a function comes from is not worth the timed saved typing std::. And the confusion often lasts more than just a moment.

Some people act like having to type std:: is an awful burden. Those people should be cast out.

The exception to this is when using the chrono_literals which to my knowledge can’t be used without using namespace (this is a crime).

For other namespaces like the verbose, nested boost ones, alias the namespace to something brief. For example boost::filesystem gets aliased to fs. Using statements in a translation unit are fine for bringing in a single symbol like a class or a function. This is sufficiently explicit.

OneEightHundred
Feb 28, 2008

Soon, we will be unstoppable!

Xerophyte posted:

It's difficult for code to touch windows.h and remain free of horror regardless of namespace practices, really.
It's 2020 and there's still no define that disables all the preprocessor defs that bolt W or A on to the end of extremely common symbol names.

ultrafilter
Aug 23, 2007

It's okay if you have any questions.


If you're developing on Windows you're going to have a bad time.

Absurd Alhazred
Mar 27, 2010

by Athanatos

ultrafilter posted:

If you're developing you're going to have a bad time.

taqueso
Mar 8, 2004


:911:
:wookie: :thermidor: :wookie:
:dehumanize:

:pirate::hf::tinfoil:

have you tried developing... on weed??

Xeom
Mar 16, 2007
I do all my deving on weed.

I'm trying to make an iterator for a data structure that has two different arrays whose data I would like the user to have access to when using a ranged based for loop. Right now my implementation is really ugly, I'm returning a Pair<type1&, type2&>, but this isn't an lvalue and generally makes things ugly when used in a ranged based for loop. Is there a better approach?

peepsalot
Apr 24, 2007

        PEEP THIS...
           BITCH!

Xeom posted:

I do all my deving on weed.

I'm trying to make an iterator for a data structure that has two different arrays whose data I would like the user to have access to when using a ranged based for loop. Right now my implementation is really ugly, I'm returning a Pair<type1&, type2&>, but this isn't an lvalue and generally makes things ugly when used in a ranged based for loop. Is there a better approach?
If the arrays are a 1:1 match, then it probably would make most sense to merge them into a single array of structs containing those two data members. This will give better data locality for cache performance etc., and you can return a reference and/or const reference to the struct from your iterator operator*().

Xeom
Mar 16, 2007

peepsalot posted:

If the arrays are a 1:1 match, then it probably would make most sense to merge them into a single array of structs containing those two data members. This will give better data locality for cache performance etc., and you can return a reference and/or const reference to the struct from your iterator operator*().

That's what I was trying to avoid because in most operations I'm interested in only one array, and it would be a lot faster to keep them separate as a struct of arrays. However that's probably the best choice if I wanna do this. That or go just use lambda functions to modify things.

ultrafilter
Aug 23, 2007

It's okay if you have any questions.


Do you actually care about the difference in performance?

Foxfire_
Nov 8, 2010

If they aren't actually adjacent in memory, a client will always be able to figure that out.

If you don't want to move them, return some adapter that has the operators you need and internally translates the accesses to the appropriate locations.

code:
class Adapter
{
public:
    Adapter(int* array1, int* array2, int initialLocation, int size)
    : array1(array1),
      array2(array2),
      location(initialLocation),
      size(size)
   {
   }

   int& Thing1() { return this->array1[this->location]; }
   int& Thing2() { return this->array2[this->location]; }

  // Operators to assign/copy Adapters,
  // Operators to move the referenced location around
  // ...
};

Xeom
Mar 16, 2007

ultrafilter posted:

Do you actually care about the difference in performance?

Probably not, but I figured I'd ask.

baby puzzle
Jun 3, 2011

I'll Sequence your Storm.
Can I make this work?

code:
static constexpr ButtonMapping arse = { sf::Keyboard::Delete, false, false, false };
I get compile error: error C2131: expression did not evaluate to a constant

And visual studio produces this: calling the default constructor for "ButtonMapping" does not produce a constant value

Where ButtonMapping is this. It has a constructor that is old code that I can't easily get rid of. If I try this with a class with no constructors, then it works.

What is it about the existence of the constructor that breaks this? This is something that has always confused me. Adding a constructor to a class seems to remove some of the default behavior. How can I add a constructor to a class without magical things happening behind the scenes?

code:
struct ButtonMapping
{
	ButtonMapping( int key = -1, bool shift = false, bool ctrl = false, bool alt = false )
		: m_Button( key )
		, m_Shift( shift )
		, m_Ctrl( ctrl )
		, m_Alt( alt )
	{}

	//...

	int m_Button = -1;
	bool m_Shift = false;
	bool m_Ctrl = false;
	bool m_Alt = false;
};

Xerophyte
Mar 17, 2008

This space intentionally left blank
Your constructor is not constexpr and I believe that code should compile if you just change the constructor to constexpr ButtonMapping( ... ). That requirement exists because in general a constructor may have arbitrary side-effects and cannot be invoked at compile time. The aggregate initialization for a struct with no user-declared constructor is constexpr, which is why it works without the constructor.

In general, you can only declare constexpr variables of types that meet the requirement of LiteralType.

matti
Mar 31, 2019

After trying getting into C++ multiple times before I recently started reading The Design and Evolution of C++ (1994) and I'm having great time just messing with the language. C++98 feels lot more like an extension to my beloved C instead of a horrid mess of templates and incomprehensible standardese (which I assume I'll learn to appreciate once I understand its motivations).

code:
//
// vbase.cpp -- Breaking virtual inheritance chains
//

#include <cstdio>

/*
 * Desired inheritance hierarchy:
 *
 *     A       A
 *    / \     / \
 *   /   \   /   \
 *  B1   B2 B1   B2
 *   \   /   \   /
 *    \ /     \ /
 *     C1     C2
 *      \     /
 *       \   /
 *        \ /
 *         D
 *
 */

//
// Assume this is code we can't modify.
//

class A {
protected:
        int v;

        A(int x) : v(x) {}
};

class B1 : public virtual A {
protected:
        B1(int x) : A(x) {}
};

class B2 : public virtual A {
protected:
        B2(int x) : A(x) {}
};

class C1 : public B1, public B2 {
protected:
        C1(int x) : A(x), B1(x), B2(x) {}
};

class C2 : public B1, public B2 {
protected:
        C2(int x) : A(x), B1(x), B2(x) {}
};


//
// Solution
//


//
// If we just inherited C1 and C2 they'd use the same instance of A. Instead
// declare them as member variables.
//

class D {
        friend void print(const D&);

        //
        // Wrap C1 and C2 and declare this class a `friend' to access their
        // protected members.
        //
        // Friend declarations of D have to be repeated as needed.
        //

        class C1 : public ::C1 {
                friend class D;
                friend void print(const D&);

                C1(int x) : A(x), ::C1(x) {}
        };

        class C2 : public ::C2 {
                friend class D;
                friend void print(const D&);

                C2(int x) : A(x), ::C2(x) {}
        };

        // Qualifier is unnecessary but helpful.
        D::C1 c1;
        D::C2 c2;

public:
        D(int x, int y) : c1(x), c2(y) {}

        void pow2()
        {
                c1.v *= c1.v;
                c2.v *= c2.v;
        }
};

void print(const D& d)
{
        std::printf("c1.v=%d, c2.v=%d\n", d.c1.v, d.c2.v);
}

int main()
{
        D d(2, 3);
        d.pow2();
        print(d); // Outputs "c1.v=4, c2.v=9"
}
(Obviously you'd avoid needing to do any of that in the first place)

e: im the friend declaration of D 👀
ee: drunkem typos

matti fucked around with this message at 19:25 on Aug 31, 2020

ultrafilter
Aug 23, 2007

It's okay if you have any questions.


The more I see the more I'm convinced that multiple inheritance is almost always a mistake.

Zopotantor
Feb 24, 2013

...und ist er drin dann lassen wir ihn niemals wieder raus...

ultrafilter posted:

The more I see the more I'm convinced that multiple inheritance is almost always a mistake.

Multiple implementation inheritance is always a mistake. Multiple interface inheritance is cool and good.

OddObserver
Apr 3, 2009

Zopotantor posted:

Multiple implementation inheritance is always a mistake. Multiple interface inheritance is cool and good.

Interface + epsilon implementation can work fine, IMHO --- i.e. interface + helper methods, since you don't want your virtuals to be non-private, do you?

Bruegels Fuckbooks
Sep 14, 2004

Now, listen - I know the two of you are very different from each other in a lot of ways, but you have to understand that as far as Grandpa's concerned, you're both pieces of shit! Yeah. I can prove it mathematically.

ultrafilter posted:

The more I see the more I'm convinced that multiple inheritance is almost always a mistake.

I'm sure there are valid use cases for it, but multiple implementation inheritance is one of those language features where
a) Someone thinks they are smarter than they are and uses it to solve a problem.
b) They produce something that seems like it works.
c) There is some sort of hosed up side-effect related to object initialization / copying the object / resource de-allocation.
d) It takes a while to fix the problem.
e) It wasn't really necessary to use the feature in the first place.

I also find that the more it's used, the harder it is to get my head around the classes involved - it can be like goto on steroids. The classes look much nicer but are just harder to reason about.

Subjunctive
Sep 12, 2006

✨sparkle and shine✨

mixins are so nice, though

ultrafilter
Aug 23, 2007

It's okay if you have any questions.


Multiple implementation inheritance can maybe be OK if the class hierarchies involved are completely orthogonal. Early in my career I worked on a multiplatform graphics engine that allowed us to design forms for a desktop application. We had one set of classes that represented the abstract logic for various controls, and another that handled everything OS-specific. The only issues with that were related to the inheritance hierarchy being deep and C++ not having a mechanism to require every class deriving from a given parent to supply its own implementation of some pure virtual function (hello object slicing!).

We probably still should've used composition, though.

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe
Multiple interface inheritance is usually a bad idea; if your interface is really that complicated, consider having a separate table of traits that you can extract statically or dynamically from the complete type.

Volguus
Mar 3, 2009

rjmccall posted:

Multiple interface inheritance is usually a bad idea; if your interface is really that complicated, consider having a separate table of traits that you can extract statically or dynamically from the complete type.

Why do you think that "multiple interface inheritance" implies a complicated interface? Since interfaces just define behaviours, a description of actions, I cannot really see how can one become complicated. The implementation may be, but it's definitely not a given. It doesn't have to be. But I may be missing something here.

Adbot
ADBOT LOVES YOU

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe
Multiple interface inheritance means that there are multiple unrelated abstract interfaces being satisfied by a single object. That is already complicated.

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